Problem F 풀이 (C++)

 문제 링크 : https://codeforces.com/contest/1547/problem/F

이 문제를 풀기 위해 알아야 할 정보는 gcd(gcd(a,b),gcd(b,c)) = gcd(a,b,c) 라는 것이다.

이 정보에 따르면 결국 길이 n의 배열 a를 k번 변환했을 때,

ai = gcd(ai, ai+1, ai+2, ..., ai+k) 가 된다.

답을 찾기 위해서는 배열 a의 모든 원소가 같아지는 시점을 찾아야 한다.

이 작업을 있는 그대로 실행한다면 시간이 너무 오래 걸린다.

이를 해결하기 위해 세그먼트 트리와 이분 탐색을 이용한다.

세그먼트 트리는 여러 개의 데이터가 연속적으로 존재할 때 특정한 범위의 데이터의 합을 구하는 경우에 많이 사용된다.

이 문제의 경우 특정 구간의 부분 gcd를 구해야 하는 경우가 많기 때문에 사용한다.

세그먼트 트리를 구간의 gcd 값으로 구성하고 배열 a를 변환하는 횟수 k를 이분 탐색으로 찾는다.

세그먼트 트리를 만들 때 구간의 범위를 a0 ~ an-1까지가 아니라, 

a0 ~ an, a0, a1, ~~ an-2 까지 고려해야 한다는 점을 유의해야 한다.

이유는 배열 a는 최대 n번까지 변환 가능하고 그렇기 때문에 배열 a의 마지막 원소 an-1의 부분 gcd 값의 최대 범위는 gcd(an-1, a0, a1, ..., an-2)가 되기 때문이다.

이분 탐색 과정에서 확인해야 할 작업은 배열 a의 모든 원소 ai에 대해 부분 gcd를 확인하여 모두 같은지를 확인하는 것이다.

여기서 부분 gcd는 각 원소마다 gcd(ai, ai+1, ..., ai+k)이고 세그먼트 트리에서 탐색한다.

세그먼트 트리를 사용할 경우 구간의 부분 gcd를 구할 때마다 걸리는 시간은 O(logn) 시간이다.

이분 탐색에서 한번 확인하는 과정은 배열의 각 원소마다 부분 gcd를 구하는데 시간이 필요하고, 이 시간은 n x logn = O(nlogn)이 된다.

이분 탐색 과정은 O(logn)이므로 총 O(nlognlogn) 시간 복잡도가 나온다.

( 세그먼트 트리를 구성하는데 걸리는 시간은 계산해보니 약 O(2n)이 나왔다. )

이 문제에서 n의 최댓값은 20만이기 때문에 logn은 최대 18보다 작고 총 시간 복잡도는 제한 시간을 넘지 않는다.


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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <math.h>
 
using namespace std;
 
int getGCD(int a, int b) {
    if (a > b)
        swap(a, b);
    if (a == 0)
        return b;
    while (b % a != 0) {
        b %= a;
        swap(a, b);
    }
    return a;
}
 
void initTree(vector<int>& tree, vector<int>& a, int index, int s, int e) {
    if (s == e) {
        tree[index] = a[s];
    }
    else {
        int m = (s + e) / 2;
        initTree(tree, a, index * 2 + 1, s, m);
        initTree(tree, a, index * 2 + 2, m + 1, e);
        tree[index] = getGCD(tree[index * 2 + 1], tree[index * 2 + 2]);
    }
}
 
int findGCD(vector<int>& tree, int index, int l, int r, int s, int e) {
    
    if (l > e || r < s) {
        return 0;
    }
    
    if (l <= s && e <= r) {
        return tree[index];
    }
 
    int m = (s + e) / 2;
    return getGCD(findGCD(tree, index * 2 + 1, l, r, s, m), findGCD(tree, index * 2 + 2, l, r, m + 1, e));
 
}
 
bool check(vector<int>& tree, vector<int>& a, int n, int num) {
 
    set<int> s;
    
    for (int i = 0; i < n; i++) {
        s.insert(findGCD(tree, 0, i, i + num, 0, a.size() - 1));
    }
 
    if (s.size() == 1)
        return true;
    else
        return false;
}
 
int main() {
 
    int t;
    cin >> t;
 
    while (t--) {
 
        int n;
        cin >> n;
 
        vector<int> a(n);
 
        for (int i = 0; i < n; i++)
            cin >> a[i];
 
        for (int i = 0; i < n - 1; i++)
            a.push_back(a[i]);
 
        int height = (int)(ceil(log2(a.size())));
        int size = 2 * (int)pow(2, height) - 1;
 
        vector<int> segTree(size);
 
        initTree(segTree, a, 00, a.size() - 1);
 
        // binary search
        int s = 0, e = n - 1, m;
        int answer = n - 1;
 
        while (s <= e) {
            m = (s + e) / 2;
            if (check(segTree, a, n, m) == true) {
                e = m - 1;
                if (answer > m)
                    answer = m;
            }
            else
                s = m + 1;
        }
 
        cout << answer << endl;
    }
 
    return 0;
}
cs

댓글