포스트

[BOJ] 31563: 수열 회전과 쿼리 (C/C++)

[BOJ] 31563: 수열 회전과 쿼리 (C/C++)

문제 31563번: 수열 회전과 쿼리

길이가 $N$인 정수 수열 $[A_1,A_2,\dots,A_N]$이 주어진다. 이때 다음 쿼리를 수행하는 프로그램을 작성해보자.

  • $1$ $k$: 수열을 오른쪽으로 $k$만큼 회전시킨다. 즉, $A_1$의 값은 $A_{N-k+1}$, $A_2$의 값은 $A_{N-k+2}$, $\dots$, $A_k$의 값은 $A_N$, $A_{k+1}$의 값은 $A_1$, $A_{k+2}$의 값은 $A_2$, $\dots$, $A_N$의 값은 $A_{N-k}$로 동시에 변한다.  
  • $2$ $k$: 수열을 왼쪽으로 $k$만큼 회전시킨다. 즉, $A_1$의 값은 $A_{k+1}$, $A_2$의 값은 $A_{k+2}$, $\dots$, $A_{N-k}$의 값은 $A_N$, $A_{N-k+1}$의 값은 $A_1$, $A_{N-k+2}$의 값은 $A_2$, $\dots$, $A_N$의 값은 $A_k$로 동시에 변한다.  
  • $3$ $a$ $b$: 수열의 $a$번째 수부터 $b$번째 수의 합을 출력한다. 즉, $\sum_{i=a}^b A_i$를 출력한다.

입력

첫 번째 줄에 수열의 길이 $N$과 쿼리의 수 $Q$가 공백으로 구분되어 주어진다.

두 번째 줄에 $N$개의 정수 $A_1,A_2,\dots,A_N$이 공백으로 구분되어 주어진다.

세 번째 줄부터 $Q$개의 줄에 걸쳐 쿼리가 한 줄에 하나씩 주어진다.

출력

$3$번 쿼리에 대한 결괏값을 한 줄에 하나씩 입력으로 주어진 순서대로 출력한다.

제한

  • $2 \le N \le 200\,000$   
  • $1 \le Q \le 200\,000$   
  • $1 \le A_i \le 10^9$   
  • $1 \le k \le N$   
  • $1 \le a \le b \le N$  입력으로 주어지는 모든 수는 정수이다.  
  • $3$번 쿼리는 한 번 이상 주어진다.

문제분석

문제 요구사항

  • 임의의 연속된 구간 합을 구해야 함.
  • 임의의 연속된 구간의 요소의 값이 변함.

따라서 변화하는 연속된 구간 합을 구하여야 함.

구현

쿼리 1,2 번에 따라 배열을 변화 시키지 않고 구간에 대응하기 위한 Bias(편향)값 추가
쿼리 3번 실행 시 범위에 Bias 추가하여 범위 지정 따라서 left>right인 case(경우) 처리 요구.
따라서 해당 방법 구현을 위하여 Segment TreePrefix Sum 두 가지 방법 활용하여 구현.

증명

Case 01: Segment Tree

길이가 $n$인 수열의 Segment Tree의 Init 시간복잡도는 대략 $\boldsymbol{4n = O(n)}$
구간 합을 구하는 Query의 시간복잡도는 $\boldsymbol{O(log\ n)}$이 된다.
구간 합을 구하는 Query의 갯수를 $q$ 라고 한다면,
따라서 Segment Tree의 전체 시간 복잡도는 $\boldsymbol{O(n)+q*O(log\ n)}$이 된다.

Case 02: Prefix Sum

길이가 $n$인 수열의 Prefix Sum수열을 구하는 시간복잡도는 $\boldsymbol{n = O(n)}$의 시간 복잡도를 가진다.
구간 합을 구하는 연산의 시간복잡도는 $\boldsymbol{O(1)}$ 이 된다.
따라서 Prefix Sum의 전체 시간 복잡도는 $\boldsymbol{O(n)+O(1)}$이 된다.

코드

Segment Tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <vector>
class SegTree {
  int nmax;
  std::vector<long long> tree;

 public:
  explicit SegTree(const std::vector<int>& v) {
    nmax = BitCeil(v.size());
    tree.assign(nmax << 1, 0);

    for (int i = 0; i < v.size(); ++i) {
      tree[i + nmax] = v[i];
    }
    for (int i = nmax - 1; i > 0; --i) {
      tree[i] = tree[i << 1] + tree[i << 1 | 1];
    }
  }

  const long long Query(int left, int right) {
    long long left_value = 0;
    long long right_value = 0;
    for (left += nmax, right += nmax + 1; left < right;
         left >>= 1, right >>= 1) {
      if (left & 1) {
        left_value += tree[left++];
      }
      if (right & 1) {
        right_value += tree[--right];
      }
    }
    return left_value + right_value;
  }

 private:
  const size_t BitCeil(size_t size) {
    size_t output_size = 1;
    while (size != 0) {
      output_size <<= 1;
      size >>= 1;
    }
    return output_size;
  }
};

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

  int N, Q;
  std::cin >> N >> Q;

  std::vector<int> v(N);
  for (auto& i : v) {
    std::cin >> i;
  }

  int bias = 0;
  SegTree seg_tree(v);

  for (int i = 0; i < Q; ++i) {
    int left, right;
    std::cin >> right >> left;
    if (right == 1) {
      bias -= left;
      bias %= N;
    } else if (right == 2) {
      bias += left;
      bias %= N;
    } else  // right ==3
    {
      left = (left + bias - 1) % N;
      left = left < 0 ? left + N : left;

      std::cin >> right;
      right = (right + bias - 1) % N;
      right = right < 0 ? right + N : right;

      if (left <= right) {
        std::cout << seg_tree.Query(left, right) << '\n';
      } else {
        std::cout << seg_tree.Query(left, N - 1) + seg_tree.Query(0, right)
                  << '\n';
      }
    }
  }

  return 0;
}

Prefix Sum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <vector>

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

  int N, Q;
  std::cin >> N >> Q;

  std::vector<int64_t> prefix_sum(N + 1);
  for (int i = 1; i < prefix_sum.size(); ++i) {
    int num;
    std::cin >> num;
    prefix_sum[i] = prefix_sum[i - 1] + num;
  }

  int bias = 0;
  for (int i = 0; i < Q; ++i) {
    int left, right;
    std::cin >> right >> left;
    if (right == 1) {
      bias -= left;
      bias %= N;
    } else if (right == 2) {
      bias += left;
      bias %= N;
    } else {
      left = (left + bias) % N;
      left = left <= 0 ? left + N : left;

      std::cin >> right;
      right = (right + bias) % N;
      right = right <= 0 ? right + N : right;

      if (left <= right) {
        std::cout << prefix_sum[right] - prefix_sum[left - 1] << "\n";
      } else {
        std::cout << prefix_sum.back() - prefix_sum[left - 1] +
                         prefix_sum[right]
                  << "\n";
      }
    }
  }
  return 0;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.