본문 바로가기

백준/세그먼트 트리

백준 16978 - 수열과 쿼리 22

728x90

https://www.acmicpc.net/problem/16978

힌트

더보기

x번째 원소 갱신을 하기 전 x - 1번째 원소 갱신을 한 세그먼트 트리의 구간합을 구할 수 있다.

 

각 쿼리를 보존하여 적절한 시기에 처리하도록 한다.


풀이

더보기

특정 시점까지 갱신이 적용되었을 때의 구간 합을 구하는 문제이다.

 

조금만 생각해보면 구간 합은 원소의 갱신에 미치는 영향이 없다는 사실을 깨달을 수 있다.

 

그렇다면 0번째 원소 갱신을 수행 후 구간 합 연산 -> 1번째 원소 갱신 수행 -> 1번째 원소 갱신을 수행 후 구간 합 연산 -> 2번째 원소 갱신 수행 ->... 이처럼 계산하면 질의마다 올바른 값을 얻을 수 있다.

 

그런데 어떻게 구현해야 될까?

 

세그먼트 트리를 초기화만 해주고 입력받는 쿼리들에 대해 바로 계산하지 않고 보존하도록 한다.

 

원소 갱신 쿼리를 보관하는 배열과 구간 합 쿼리를 보관하는 배열에 각각 넣어주면 된다.

 

구간 합 쿼리를 보관할 때는 다음 2차원 선형 자료구조를 이용하는 것이 적당할 것이다.

 

query_sum[x] := x번째 원소 갱신이 끝난 다음 구간 합을 구하는 쿼리를 모은 벡터

 

이때 해당 쿼리의 번호(index)도 같이 보관하도록 한다.

 

이러면 구간 합 -> 갱신 -> 구간 합 -> 갱신... 과정을 배열 순회로 구현할 수 있게 된다.

 

query_sum을 순회하면서 현재 index에 해당하는(index번째 원소까지 갱신했을 경우) 구간 합을 구해 쿼리 번호와 함께 정답을 모아두는 벡터에 넣도록 한다.

 

현재 index의 구간 합을 모두 구했으면 index + 1번째 원소 갱신을 수행한다.

 

그다음 index + 1에 해당하는 구간 합을 모두 구한다... 이를 반복하면 모든 구간 합 쿼리의 결과를 얻을 수 있다.

 

마지막으로 정답을 모은 벡터를 쿼리 번호를 기준으로 정렬하여 순서를 복원한 후 차례로 답을 출력하면 된다.

 

이렇게 입력으로 들어오는 쿼리에 대해 바로 반응하지 않고 전처리를 거친 후 계산하는 것을 오프라인 쿼리라고 부른다.

 

전체 코드

#pragma GCC target("avx,avx2,fma")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pil = pair<int, ll>;

vector<vector<int>> discreate_sum[100001];
vector<pii> discreate_query;
vector<pil> res;
int ar[1000001];
int n, q;

inline int mid(int s, int e) { return s + (e - s) / 2; }

ll init(vector<ll> &tree, int node, int start, int end)
{
  if (start == end) return tree[node] = ar[end];
  int m = mid(start, end);
  return tree[node] = init(tree, node * 2, start, m) + init(tree, node * 2 + 1, m + 1, end);
}

void update(vector<ll> &tree, int node, int start, int end, int idx, ll diff)
{
  if (start > idx || end < idx) return;
  tree[node] += diff;
  if (start != end)
  {
    int m = mid(start, end);
    update(tree, node * 2, start, m, idx, diff);
    update(tree, node * 2 + 1, m + 1, end, idx, diff);
  }
}

ll sum(vector<ll> &tree, int node, int start, int end, int l, int r)
{
  if (start > r || end < l) return 0;
  if (l <= start && end <= r) return tree[node];
  int m = mid(start, end);
  return sum(tree, node * 2, start, m, l, r) + sum(tree, node * 2 + 1, m + 1, end, l, r);
}

int main()
{
  cin.tie(nullptr);
  ios::sync_with_stdio(false);

  cin >> n;

  for (int i = 1; i <= n; ++i)
    cin >> ar[i];
  vector<ll> segtree(n * 4);
  init(segtree, 1, 1, n);
  
  cin >> q;

  for (int i = 0; i < q; ++i)
  {
    int a, b, c, d;
    cin >> a;

    if (a == 1)
    {
      cin >> b >> c;
      discreate_query.push_back({b, c});
    }
    else
    {
      cin >> b >> c >> d;
      discreate_sum[b].push_back({c, d, i});
    }
  }

  for (int i = 0; i <= q; ++i)
  {
    for (int j = 0; j < discreate_sum[i].size(); ++j)
    {
      res.push_back({discreate_sum[i][j][2], sum(segtree, 1, 1, n, discreate_sum[i][j][0], discreate_sum[i][j][1])});
    }

    if (i >= discreate_query.size())
      continue;
    ll diff = discreate_query[i].second - ar[discreate_query[i].first];
    update(segtree, 1, 1, n, discreate_query[i].first, diff);
    ar[discreate_query[i].first] = discreate_query[i].second;
  }

  sort(res.begin(), res.end());

  for (int i = 0; i < res.size(); ++i)
    cout << res[i].second << '\n';
}

제출 기록

728x90

'백준 > 세그먼트 트리' 카테고리의 다른 글

백준 14287 - 회사 문화 3  (1) 2022.06.27
백준 17408 - 수열과 쿼리 24  (0) 2022.04.05
백준 1615 - 교차개수세기  (0) 2021.08.18
백준 12844 - XOR  (0) 2021.08.06
백준 1280 - 나무 심기  (0) 2021.08.04