알고리즘/algospot

Algospot HABIT

꾸준함. 2018. 6. 25. 16:51

문제 링크입니다: https://algospot.com/judge/problem/read/HABIT


접미사 배열(Suffix Array)를 이용하여 푸는 문제였습니다.

부분 문자열이 여러 번 출현할 경우 항상 인접한 접미사들의 접두사로 출현합니다. 

즉, 배열에서 인접한 모든 접미사 쌍에 대해 최장 공통 접두사를 계산하면 두 번 이상 출현하는 부분 문자열의 최대 길이를 알 수 있습니다.

주어진 문자열에서 부분 문자열 S가 출현하는 위치가 A 군데 있다면 S는 A개의 접미사의 접두사가 됩니다.(prefix of suffix)

따라서 첫 번째 접미사와 마지막 접미사의 공통 접두사는 항상 S를 포함합니다.


종만북을 보면서 문자열 파트가 제일 어렵게 느껴졌던 것 같습니다.

생소한 개념도 많이 나왔고 대부분의 사람들이 어렵다고 하는 것을 보면 문자열 파트는 4~5번 정도 다시 봐야지 완벽히 이해가 되지 않을까 싶습니다!


#include <iostream>

#include <vector>

#include <string>

#include <algorithm>

using namespace std;

 

//각 접미사들의 첫 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];

        }

};

 

//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;

}

 

//s[i..] s[j..]의 공통 접두사의 최대 길이 계산

int commonPrefix(const string &s, int i, int j)

{

        int k = 0;

        while (i < s.size() && j < s.size() && s[i] == s[j])

                 i++, j++, k++;

 

        return k;

}

 

//k번 이상 출현하는 s의 부분 문자열 중 최대 길이를 찾는다

int longestFrequent(int k, const string &s)

{

        vector<int> a = getSuffixArray(s);

        int result = 0;

        for (int i = 0; i + k <= s.size(); i++)

                 result = max(result, commonPrefix(s, a[i], a[i + k - 1]));

        return result;

}

 

int main(void)

{

        int test_case;

        cin >> test_case;

 

        for (int i = 0; i < test_case; i++)

        {

                 int K;

                 string S;

 

                 cin >> K >> S;

                 cout << longestFrequent(K, S) << endl;

        }

        return 0;

}


개발환경:Visual Studio 2017


지적, 조언, 질문 환영입니다! 댓글 남겨주세요~


[참고] 프로그래밍 대회에서 배우는 알고리즘 문제해결전략

반응형

'알고리즘 > algospot' 카테고리의 다른 글

Algospot FORTRESS  (0) 2018.06.26
Algospot TRAVERSAL  (0) 2018.06.26
Algospot JAEHASAFE  (0) 2018.06.22
Algospot PALINDROMIZE  (0) 2018.06.20
Algospot NAMING  (0) 2018.06.20