이사 중...


===============================================



하다가 멘탈 깨져서 정리함. 


서픽스 어레이의 n^2logn 풀이는 아주 간단하다. 소스 코드만 정리해보자면 ... 



1
2
3
4
5
6
7
8
9
10
vector<int> getSuffixArray(const string& s){
    vector<int> suffix; 
    //suffix배열을 초기화
    for (int i = 0; i < s.size(); ++i) suffix.push_back(i); 
    //문자열을 비교( 람다함수 사용 )
    sort(suffix.begin(), suffix.end(), [&s](int a, int b){
        return strcmp(s.c_str() + a, s.c_str() + b) < 0
    });
    return suffix; // 리턴
}
cs

 


아아아아아주 쉽다. 오히려 람다함수가 더 어려운듯...


방식자체는 설명하자면 n개의 문자열을 죄다 때려박은 후

(메모리를 적게 사용하기 위해서 인덱스만 때려박음 : 4번째 줄)


그 해당 문자열을 정렬한다. 근데 정렬하는데 드는 비용이 nlogn이고 하나의 문자열을 비교하는 비용이 n이므로 n^2logn이다. 

굉장히 못써먹을 시간복잡도라고 생각할 수 있지만 사실 문자열을 비교하는데 일반적인 경우, 항상 n이 걸리지는 않기 때문에 그럭저럭 쓸만하다.

... 인풋이 aaaaaaaaaaaaaaaaaaaa... 만 아니라면... 


이것보다 좋은 알고리즘으로는 맨버-마이어스의 알고리즘이 있다. 

이 알고리즘의 시간복잡도는 n * log n * log n  n을 로그로 떨군건데 

난 멍청해서 이걸 이해하는데 엄청 오래걸렸다.

내가 구글링을 잘 못하는 걸 수도 있는데 제대로 정리된 블로그가 없어... 그래서 정리함. 





 

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
std::vector<int> getSuffixArray(const string& s) {
    int n = s.size();
    
    //idx + t 까지 비교한다!
    int t = 1
 
    //group[i]는 t까지 비교했을 때 i의 그룹 번호 
    vector<int> group(n + 1);
    //t==0일때를 대신한 거라고 봐도 무방... 
    for (int i = 0; i < n; ++i) group[i] = s[i];
    group[n] = -1;
 
    //서픽스를 담을 배열
    vector<int> suffix(n);
    //서픽스를 초기화
    for (int i = 0; i < n; ++i) suffix[i] = i;
 
 
    while (t < n) {
 
        //비교함수 아주 중요함
        auto cmp = [&group, t](int a, int b){
            if (group[a] != group[b])
                return group[a] < group[b];
            return group[a + t] < group[b + t];
        };
 
        //정렬
        std::sort(suffix.begin(), suffix.end(), cmp);
 
        t *= 2;
 
        //t가 n보다 크거나 같으면 종료
        if (t >= n) break;
 
        //그룹 재지정
        vector<int> newGroup(n + 1);
        newGroup[n] = -1;
        newGroup[suffix[0]] = 0;
        for (int i = 1; i < n; ++i){
            if (cmp(suffix[i - 1], suffix[i])){
                newGroup[suffix[i]] = newGroup[suffix[i - 1]] + 1;
             }else
                newGroup[suffix[i]] = newGroup[suffix[i - 1]];
        }
        group = newGroup;
    }
    return suffix;
}
cs

 



람다함수 [&group,t] <= group은 레퍼런스로 참조하고 t는 값을 복사해서 사용한다. 

나름대로 정리하자면 

s ? 구하려는 서픽스 배열의 target 스트링

group[i] ? i번째 접미사가 가지는 그룹 번호(index순) 

suffix[i] ? t까지 봤을때 i번째 접미사(사전 순)


t상태일때 어디까지 정렬되냐면 글자수가 2t일때까지 정렬된다.(즉 1일때는 글자수가 2개까지 정렬)

근데 이 그룹이라는 놈이 엄청나게 많은 것을 함축 하고 있는데 

group[a]는 사실 string의 [a...a+t)까지 비교한 결과의 상대 값이 저장되어 있다고 보면 된다.

이걸 이해하는데 이틀걸림. 


22~ 26줄의 compare함수를 보자. 


case : group[a] != group[b]

group[a]와 group[b]가 다를 때 group[a] < group[b]를 리턴한다. 

현재 t상태일때 벌써부터 group값이 다르다는 것은 이미 비교가 끝나있다는 것이다. 

그러니 a+t번째는 비교해볼 가치도 없음. 


case : group[a]와 group[b]가 같다.

string의 [a...a+t) [b...b+t)의 비교결과가 같다는 것이다. 

그럼이제 이걸 신경 쓸 필요가 없지.

[a+t...a+2t)와 [b+t...b+2t) 만 비교하면 된다. 

근데 이걸 일일이 다 비교하면 시간복잡도가 어마어마하게 증가한다. 

비교 할때마다 t만큼 추가 시간이 걸리니 말이다.

그리고 다행스럽게도 이 결과는 이미 저장되어 있다. 


어디에?

group[a+t]와 group[b+t]에!


그렇기 때문에 group[a+t]와 group[b+t]를 비교한 값을 리턴해주면 된다.


이 비교함수로 정렬이 완료되었으면 이제 이 결과를 다시 그룹에 반영해줘야 하는데 

이부분이 37~46줄이다. 

그룹의 값은 상대값이기 때문에 suffix의 첫번째 값은 0으로 설정하도록 하자. 

이제부터 suffix[n-1]까지 갱신해주는데 비교했는데 

이 비교의 기준은 이전 group과 t이다.(아직 갱신되기 전)

(실수로 t를 값이아니라 레퍼런스로 참조해봤는데 재밌는 일이 일어났다 ㅎㅎ)


case : suffix[i-1]과 suffix[i]를 비교

비교값이 더크데! <- 당연히 더 커야지 비교한 결과가... 

즉 이미 비교가 완료된 항목임.

그러니까 상대값도 1증가시켜줌. 

case : 현재까지 문자열을 사전순으로 비교할 수 없다. 

suffix배열은 t까지 진행했을때 정렬되어 있어야한다. 

즉 이부분은 아직 정렬이 제대로 이뤄지지 않았다는 것이고

따라서 아직까지는 suffix[i]와 suffix[i-1]은 누가더 사전순으로 우선인지 알 수 없다.

즉 suffix[i]와 suffix[i-1]은 길이가 2t일때까지 같았다는 것 

그룹번호를 같게 유지해서 다음 시퀀스에 비교할 수 있게 해두자.

(서픽스 어레이의 특성상 모든 문자열의 길이가 다르기때문에 같은건 존재할수없다.)


다른사람이 이해가 됐을지 모르겠다. 


내가 나중에 봤을때 이해할 수 있을 정도로만 정리해둔거라... 


틀린부분 있다면 지적부탁드려용.


참고 : 알고리즘 문제해결 전략 2권 suffix array파트 참조


p.s. suffix array관한 풀이는 최대 n까지 줄일 수 있고 이것보다 쉬운 nlogn풀이도 존재합니다.

p.s.2 그니까 저건 나중에 공부해서 포스팅할게요. 이거하는데도 지침 + sa와 한쌍인 lcp알고리즘도 다음에... 저가 너무 멍청해서


======================================================


이전 블로그에 있던 글 옮겨왔습니다. ㅎ 

'Problem Solving > 이론' 카테고리의 다른 글

nCr mod p 구하기  (2) 2018.04.26
거듭 제곱의 합  (2) 2016.10.22

딱히 필요해서 정리하는 것은 아닌데 이런 것이 정리하고 공유하는게 다른 분들이 공부하기 더 쉬울 것 같아 포스팅 한다.


1. dovelet : www.dovelet.com

더블릿은 부분유료 사이트이다. 모든 문제를 볼수는 있다. 다만 1,2,3계단만 공짜로 채점 받을 수 있다. 즉 4단계 이상의 문제를 제출해 볼 수는 없다. 1,2,3단계는 기본적인 입출력, 반복문등을 연습하는 기초적인 단계이므로 알고리즘을 공부하기로 마음 먹은 사람에게 거의 필요없는 문제들이 많기 떄문에( 물론 코딩 속도를 올리는 용도로는 효과적이라는 이야기도 있다. ) 사실상 유료사이트라고 봐도 될 것 같다.


특징

장점

      1.  계단별로 특정 알고리즘의 설명, 수도코드, 소스코드등이 첨부되어 있어 혼자 공부하기 쉽다. 
      2. 특정 알고리즘을 배운 다음에 그 밑에 그 알고리즘으로 풀리는 문제들이 있다. 그 문제들을 풀어보며 감을 익힐 수 있다. 
      3.  만약 제출한 코드가 틀렸을 경우 틀린 testcase를 볼 수 있다. 

단점

      1.  제출한 코드가 틀렸을 경우에 틀린 testcase를 보여주는게 항상 좋은 것은 아니다.(실전 처럼 연습할 때 불편할 경우가 많다.)
      2.  테스트케이스가 빡빡하지 않다. 통과되지 말아야할 소스코드가 통과되는 경우가 왕왕 있었던 것으로 기억한다.
      3.  제대로 공부를 할 요량으로 이 사이트를 사용하려면 3만원? 정도되는 가격을 지불해야한다. (평생 사용 요금이다.)



2. 백준 온라인 저지 : www.acmicpc.net 

백준은 완전 무료사이트로 모든 문제를 공짜로 풀어볼 수 있다. 유아이도 상당히 이쁜 편이다. 직관적이고 상당히 많은 문제를 풀어 볼 수 있다. acmicpc관련 문제 + 올림피아드 관련 문제들이 많아 이 쪽 준비를 하는 사람들에게는 상당히 도움이 되는 사이트다. 질문 게시판들도 있어 모르는 문제를 물어볼 수 있다. 갓갓갓님들이 잘 답변해주신다고....



특징

장점

      1. 공짜다.
      2. 문제양이 상당히 많다.
      3. UI가 이쁘다.
      4. 갓갓갓님들이 주로 상주하는 사이트이고, 또 질문을 올릴 경우 이 갓갓갓 분들이 답변을 잘해주신다.
      5. 테스트 데이터가 빡빡한 편이라 어셉이 나지 말아야할 코드가 어셉이 나는 경우는 거의 없다. 있다고 해도 사람들이 찾아서 데이터를 보강해준다.  
      6. 그룹을 만들어 아는 사람들끼리 가상의 대회를 열어볼 수 있다.

단점

      1.  아직 분류 기능이 활성화 되어 있지 않아서 분류되어 있는 문제가 적다. 특정 알고리즘으로 푸는 문제를 분류탭에서 찾아보면 문제가 너무 적거나 말도안되게 어렵거나... 
      2. 친구기능이 없다. ㅠ

기타

      1. icpc.me/[문제번호] 라는 url로 접속하면 해당 문제로 바로 이동 가능하다.
         예) 자물쇠 문제 : icpc.me/1514
      2. BOJ 슬랙이 있다. 질문을 게시판으로 해도되지만 슬랙에 올려도 된다. 슬랙에 올리는 편이 더 자세한 답변을 들을 수 있는 것 같다.  



3. UVA, live archive: https://uva.onlinejudge.org/ ,https://icpcarchive.ecs.baylor.edu/

 되게 짜증나는 외국 사이트인데 처음 시작하는 사람에게는 별로 추천해주고 싶지는 않다. 문제양은 정말 많지만... 데이터 자체가 틀렸을 경우가 왕왕이아니라 아주 많다. (정답인 코드가 틀린다던가 하는) 뿐만아니라 개행문제등을 이유로 틀렸습니다가 뜨는 문제들이 너무많다.(스트레스... ) 


특징

장점

      1. 공짜다.
      2. 문제양이 상당히 많다.
      3. acmicpc관련 문제는 거의 대부분 있다.

단점

      1. 내가 알기로 문제 분류기능 따위는 없다. UI도 불편... 
      2. acm문제들 중 예선문제는 수록되어 있지 않다.
      3. acm문제들이 대부분 수록되어있으나 데이터가 틀린 경우가 너무 많다.
      4. 틀리기만 하는 것도아니고 데이터가 약한 것도 너무 많다.
      5. 개행등으로 틀렸습니다가 뜨는 경우가 많다.

기타

      1. 가상의 대회를 열어볼 수 있다. https://icpcarchive.ecs.baylor.edu/uhunt/
      2. 근데 위에 저게... 시간이나 문제링크등이 이상하게 되어있다. 짜증남... 


4. codeforces, topcoder : www.codeforces.com  , www.topcoder.com

 온라인 저지라기보다 컨테스트 사이트다. codeforces는 러시아 것이고 topcoder는 미국 것이다. 문제는 둘다 영어로 제공되며 게임처럼 레이팅을 경쟁할 수 있다. 레이팅별로 색깔이 달라지는데 빨간색, 노란색, 보라색 티어(?)가 있다. 롤로 치면 마스터, 다이아, 플레티넘 같은 것이라고 보면 된다. 레이팅 올리는 재미가 있으며 정규 컨테스트에서만 레이팅이 변경된다. 둘다 가상의 컨테스트를 열어볼 수 있고 DIV1/DIV2로 나누어져있는데 DIV1은 상위리그, DIV2는 하위리그다. 처음 시작 하는 분들은 DIV2 대회로 시작하시면 되겠다.

특징

장점

      1. 공짜다.
      2. 게임처럼 참가할 수 있다. 롤 레이팅 올리는 느낌이다. 
      3. 에디토리얼이라고 문제풀이가 제공된다. 따라서 보면서 공부할 수 있다.
      4. 둘다 핵 개념이 있는데 컨테스트중에 제공되는 테스트 케이스는 약한 테스트 케이스다. 따라서 어셉이나도 최종적으로 어셉이 아닐 수 있다. 이런 부분 때문에 각별히 예외나 시간복잡도 신경써야한다. 다른사람이 여러분의 소스코드를 읽고 틀린 데이터를 만들어 여러분의 소스코드를 공개할 수 있다.
      5. 반대로 문제를 잘 못풀어도 핵을 잘해서 점수를 딸 수 있다. 다른 사람의 소스코드에서 예외를 찾아 던지면 추가 점수를 얻을 수 있다.  

단점

      1.  소스코드를 읽기가 힘들다. 각종 define과 typedef의 연속... 
      2.  콘테스트에서 영어에 의해서 디버프를 받을 수 밖에 없다. 부족한 영어실력을 알고리즘 실력으로 극복해야한다. ㅠㅠ
      3.  핵당하면 괜히 기분이 나쁘다.
      4. 사이트가 빠른 편은 아니다. (아무래도 한 컨테스트당 몇천명의 사람이 몰리기 때문인 것 같다.) 




자세히 소개는 안했지만 다른  좋은 곳들도 많다. 

주로 사용하는 사이트만 리뷰하고 자주 사용하지 않은 사이트들은 정리하지 않았다. 잘못된 정보를 줄 수도 있기 때문에...

아래는 그외 사이트들이다.


알고스팟 : www.algospot.com 

수학관련 문제 - 프로젝트 오일러 :  http://projecteuler.net/ 

수학관련 문제 - 프로젝트 오일러(한국) : http://euler.synap.co.kr/ 

아주대학교 알고리즘 온라인 저지 라비다 : http://www.lavida.us/ 



+ Recent posts