728x90

 0. [c++] 백준  - 


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


 1. 풀이


처음 이 문제를 접했을 때는 생각보다 간단하게 구현을 할 수 있을 것 같았다. 하지만, 내 기대와 같이 문제가 쉽게 해결되지 않았다.

문제의 입력은 최대 500,000 까지의 입력이여서, 시간복잡도를 신경써야하는 문제였다.


처음 구현을 할때, Suffix Array의 시간 복잡도는 O(nlgn)의 크기로 시간내에 구동이 가능하지만, LCP Array를 단순하게 구현하니 시간 초과를 받게 되었다.

처음 구현은 

1
2
3
4
5
6
7
8
9
int N = temp.size()
 
for (int i = 1; i < N; i++) {
    int count = 0;
 
    for(int j = suffixArray[i -1], k=0; temp[i + k] == temp[j + k]; k++);
    cout << count << " ";
        
}
cs

과 같이 구현하였는데, 이와 같이 구현하니 시간복잡도가 O(n^2)로 문제가 있었다.


이를 해결하기 위해서 생각을 해보았는데, KMP의 알고리즘을 생각해보면, 반복되는 계산을 줄이기 위해 pi라는 배열을 만들어 필요 없는 부분을 뛰어 넘어가는 구현을 하는 것을 보았었다.

이를 비슷하게 활용하면 되지 않을까? 라는 생각으로 나아갔다.


결과적으로 이러한 논리가 성립하였는데, LCP Array의 계산은 가장 긴 문자열부터 시작을 하는 것이었다.


 suffixArray에 담긴 가장 긴 문자열(size = N)과 그 아래 문자열(길이는 랜덤)을 비교한다. 그리고 일치한 문자열의 길이가 k(k는 0이 아닌 정수)라 가정하자.

 그 다음으로 긴 문자열(size = N-1)과 그 아래 문자열을 비교할 때, 그 길이는 최소 k-1가 되는 것을 보일 수 있었다.


뭐 이게 성립하는 이유는 suffixArray가 사전순으로 정렬이 되어있고, 자신의 아래에 있는 문자열은 자신과 첫 글자가 일치하는 부분이 없거나, 자신보다 더 긴 경우 2가지로 나뉘어진다.


문제에 banana예시를 생각해보면, 

a

ana

anana

banana

na

nana


뭐, 이런식으로 ana에서는 3, na에서는 2, a에서는 1이 ACP Array에 담기게 된다


이와 같은 논리로 문제를 해결 할 수 있도록 코드를 구현한 것은 아래 소스코드에 있다.




 2. 소스코드


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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include<vector>
#include<algorithm>
#include<string>
#include<iostream>
 
using namespace std;
 
int pos[500001];
 
//각 접미사들의 첫 t글자를 기준으로 한 그룹 번호가 주어질 때,
//주어진 두 접미사를 첫 2*t글자를 기준으로 비교한다.
//group[]은 길이가 0인 접미사도 포함한다.
struct Comparator {
    const vector<int>& group;
    int t;
    Comparator(const vector<int>& _group, int _t) : group(_group), t(_t) {
    }
    bool operator() (int a, int b) {
        //첫 t글자가 다르면 이들을 이용해 비교한다.
        if (group[a] != group[b]) return group[a] < group[b];
        //아니라면 S[a+t..]와 S[b+t..]의 첫 t글자를 비교한다.
        return group[a + t] < group[b + t];
 
    }
};
 
 
////코드 20.10 접미사 배열을 계산하는 맨버와 마이어스의 알고리즘
 
//s의 접미사 배열을 계산한다.
vector<int> getSuffixArray(const string& s) {
 
 
    int n = s.size();
    //group[i]=접미사들을 첫 t글자를 기준으로 정했을 때,
    //            s[i..]가 들어가는 그룹 번호.
    //t=1일 때는 정렬할 것 없이 S[i..]의 첫 글자로 그룹 번호를
    //정해 줘도 같은 효과가 있다.
    int t = 1;
    vector<int> group(n + 1);
    for (int i = 0; i < n; i++) group[i] = s[i];
    group[n] = -1;
    //결과적으로 접미사 배열이 될 반환 값. 이 배열을 lg(n)번 정렬한다.
    vector<int> perm(n);
    for (int i = 0; i < n; i++) perm[i] = i;
    while (t < n) {
        //group[]은 첫 t글자를 기준으로 계산해 뒀다.
        //첫 2t글자를 기준으로 perm을 다시 정렬한다.
        Comparator compareUsing2T(group, t);
        sort(perm.begin(), perm.end(), compareUsing2T);
        //2t글자가 n을 넘는다면 이제 접미사 배열 완성!
        t *= 2;
        if (t >= n)
            break;
        //2t글자를 기준으로 한 그룹 정보를 만든다.
        vector<int> newGroup(n + 1);
        newGroup[n] = -1;
        newGroup[perm[0]] = 0;
        for (int i = 1; i < n; i++)
            if (compareUsing2T(perm[i - 1], perm[i]))
                newGroup[perm[i]] = newGroup[perm[i - 1]] + 1;
            else
                newGroup[perm[i]] = newGroup[perm[i - 1]];
        group = newGroup;
    }
    return perm;
}
 
 
 
//계산의 반복을 줄이기 위해 구상을 해보자.
//접두사의 길이를 비교할 때 매번 자기 아래의 수와 비교를 하는 방법이 존재한다.
//하지만, 필요 없는 반복이 계속된다는 느낌이 든다.
//이것은 KMP에서 pi배열과 비슷하게 계산을 줄일 수 있는 방법이 존재 할 것 같다.
//결과적으로 
vector<int> LCPArray(const string& S, const vector<int>& suffixArray) {
    int N = S.size();
    vector<int> ret(N, 0);
    //길이가 긴 순서대로 비교를 할 것이다.
    //이를 위해 순서대로 자신의 위치를 표시해주는
    //pos[]배열을 만들어주었다.
    for (int i = 0; i < N; i++)
        pos[suffixArray[i]] = i;
 
    for (int i = 0, k=0; i < N; i++, k=max(k-10)) {
        //suffixArray의 마지막은 비교가 불가능하다.
        if (pos[i] == N - 1continue;
 
        //바로 아래의 접미사와 비교해서 몇 개의 글자가 일치하는지 비교한다.
        for (int j = suffixArray[pos[i] + 1]; S[i + k] == S[j + k]; k++);
        ret[pos[i]] = k;
    }
    return ret;
}
 
 
int main() {
 
    ios_base::sync_with_stdio(0);
    cin.tie(0);
 
    string temp;
    cin >> temp;
    vector<int> suffix = getSuffixArray(temp);
    vector<int> LCP = LCPArray(temp, suffix);
    int N = temp.size();
    for (int i = 0; i < N; i++) {
        cout << suffix[i]+1 << " ";
    }
    cout << "\nx ";
    for (int i = 0; i < N-1; i++) {
        cout << LCP[i] << " ";
    }
 
 
    return 0;
}
 
 
 
 
 
 
 
 
cs


 3. 참고


- 맴버와 마이어스의 알고리즘 참고

구종만, 「프로그래밍 대회에서 배우는 알고리즘 문제 해결 전략」, 인사이트, 2012, p.666~667

- LCP Array 구하는 알고리즘

https://blog.naver.com/PostView.nhn?blogId=kks227&logNo=221028710658


질문이나 지적 있으시면 댓글로 남겨주세요~

도움 되셨으면 하트 꾹!


+ Recent posts