"일반적인 문자열 알고리즘 요약"

일반적인 문자열 알고리즘 요약

문자열 해쉬

일반적으로 다항식 사용 \ (\ mathrm {해시} \ ) 양의 정수에 문자열을 매핑 방법의 역량 강화를.

\ [{| S |} | s_i | f는 (S) = \ sum_가 ^ {i가 1 =} \ 시간 P ^ I \ (\ BMOD \ P) \]

지원 \ (O (1) \) 삽입 문자의 끝, \ (O (1) \) 추출 부분 문자열 \ ({해시} \ \ mathrm ) 값.

아마 각 쿼리에 대한 충돌 속도 \ (\ FRAC {1} { P} \) 정도, 쿼리가 더 자주, 듀얼 모드의 수를 사용할 수있는 경우 \ (\ mathrm {해시} \) .

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20 , Mod = 998244353 , P = 131;
inline int inc(int a,int b) { return a + b >= Mod ? a + b - Mod : a + b; }
inline int mul(int a,int b) { return 1LL * a * b % Mod; }
inline int dec(int a,int b) { return a - b < 0 ? a - b + Mod : a - b; }
inline void Inc(int &a,int b) { a = inc( a , b ); }
inline void Mul(int &a,int b) { a = mul( a , b ); }
inline void Dec(int &a,int b) { a = dec( a , b ); }
int Pow[N],val[N],n,m; char s[N];
inline int GetHash(int l,int r) { return dec( val[r] , mul( val[l-1] , Pow[r-l+1] ) ); }
int main(void)
{
    scanf( "%s" , s+1 );
    n = strlen( s + 1 );
    Pow[0] = 1;
    for (int i = 1; i <= n; i++)
        Pow[i] = mul( Pow[i-1] , P ) , val[i] = inc( s[i] - 'a' , mul( val[i-1] , P ) );
    scanf( "%d" , &m );
    for (int i = 1; i <= m; i++)
    {
        int l1,l2,r1,r2;
        scanf( "%d%d%d%d" , &l1 , &r1 , &l2 , &r2 );
        GetHash(l1,r1) == GetHash(l2,r2) ? puts("Yes") : puts("No");
    }
    return 0;
}

트라이 트리

결정적 유한 상태 자동 장치 만 식별 문자열의 집합으로 인식 \ (S를 \) 모든 문자열.

지원 \ (O는 (| S |) \) 문자열에 삽입, \ (O (|) | s의 \) 문자열을 검색 할 수 있습니다.

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20;
struct Trie
{
    int e[N][26],end[N],tot;
    Trie(void) { tot = 1; }
    inline void Insert(char *s)
    {
        int n = strlen( s + 1 ) , p = 1;
        for (int i = 1; i <= n; i++)
        {
            int c = s[i] - 'a';
            if ( !e[p][c] ) e[p][c] = ++tot;
            p = e[p][c];
        }
        end[p] = true;
    }
    inline bool Query(char *s)
    {
        int n = strlen( s + 1 ) , p = 1;
        for (int i = 1; i <= n; i++)
        {
            int c = s[i] - 'a';
            if ( !e[p][c] ) return false;
            p = e[p][c];
        }
        return end[p];
    }
};

크 누스 - 모리스 - 프랫 알고리즘

캐릭터의 정의 (\ mathrm {국경} \ \ ) 일반적인 접두사와 접미사.

함수 정의 프리픽스 문자열 \ [\ PI (p) = \ MAX_ {S (1, t) = S (p-t + 1, P)} \ {t \} \]

문자열의 의미 \ (S \) 접두사 \ (S_P \) 까지 \ (\ mathrm {국경} \ ) 길이. 순회 문자열, 가장 긴상의 위치에서 때마다 (\ \ mathrm {국경} \ ) 경기가 이동하지 않을 경우, 후방으로 경기의 시작 (\ mathrm {국경} \ \ ) , 성공적으로 일치 할 때까지 추구하는 모든 접두어 문자열의 함수.

정의 에너지 함수 \ (\ 피 (P)는 \ ) 접두사 문자열 (\ S_P) \\ (\ mathrm {테두리} \ ) 받는 따른 길이 \ (\ mathrm {누스 모리스 -Pratt} \) 알고리즘 이 \ (\ 피 (P) \ 당량 \ 피. (1-P) +1 \) 폭력 점프 경우 \ (\ mathrm 테두리} {\) 의 전위가 감소 전체 시간 복잡도가 발견 (O (\ N- 형) \) .

접두어는 문자열 함수를 얻어지면, 싱글 모드가 문자열 일치 문자열을 달성 할 수 있고, 그 최장의 불일치이다 ({테두리} \ \ mathrm \ ) 매칭에서 다시 시작할 수는 시간 복잡도는 \ (O (N-m +) \) , 유사한 분석.

#include <bits/stdc++.h>
using namespace std;
const int N = 1000020;
int n,m,fail[N]; char s[N],t[N];
int main(void)
{
    scanf( "%s\n%s" , s+1 , t+1 );
    n = strlen( s + 1 ) , m = strlen( t + 1 );
    for (int i = 2 , j = 0; i <= m; i++)
    {
        while ( j && t[j+1] != t[i] ) j = fail[j];
        j += ( t[j+1] == t[i] ) , fail[i] = j;
    }
    for (int i = 1 , j = 0; i <= n; i++)
    {
        while ( j && ( t[j+1] != s[i] || j == m ) ) j = fail[j];
        j += ( t[j+1] == s[i] );
        if ( j == m ) printf( "%d\n" , i - m + 1 );
    }
    for (int i = 1; i <= m; i++)
        printf( "%d%c" , fail[i] , " \n"[ i == m ] );
    return 0;
}

크 누스 - 모리스 - 프랫 자동 기계

문자열을 \ (S \) 은 define, \ (\ mathrm {KMP} \ ) 만족 마톤을 :

\ (1 \) 의 상태 번호 \ (N- +. 1 \) .
\ (2 \) 모든 접두사를 식별합니다.
\ (3 \) 전달 함수 \ (\ 델타 (P, C ) \) 상태 \ (P \) 연결된 문자 프리픽스에 대응 \ (C \) 긴 후 \ (\ mathrm {테두리} \ ) 의 총수 프리픽스 상태 대응 .

생성자 메소드 \ (\ mathrm {누스 모리스 -Pratt} \) 알고리즘 마찬가지로, 시간 복잡도는 \ (O (N- \ 시그마) \) .

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20;
struct KMPAutomaton
{
    int trans[N][26],n;
    inline void Build(char *s)
    {
        n = strlen( s + 1 ) , trans[0][s[1]-'a'] = 1;
        for (int i = 1 , j = 0; i <= n; i++)
        {
            for (int k = 0; k < 26; k++)
                trans[i][k] = trans[j][k];
            trans[i][s[i]-'a'] = i + 1;
            j = trans[j][ s[i] - 'a' ];
        }
    }
};

아호-Corasick 자동 기계

결정적 유한 상태 자동 장치는, 지정된 문자열의 집합의 모든 접미사 인식 (S \) \ 문자열입니다.

첫째, 우리는 초기화 \ (\ mathrm {아호-Corasick } \) 컬렉션에 지정된 문자열에 대한 자동 기계 \ (S \) \ (\ mathrm {트리는} \ ) 다음에 따라 트리 (\ \ mathrm {대금을} \ ) 전송 시퀀스 생성자 함수 \ (\ 델타 \) .

우리는 각각의 상태가이 정의 \ (\ mathrm \ {실패} ) 포인터를, \ (\ mathrm {} 실패 (X) = Y \) 경우에만, 상태 \ (Y \) 문자열 상태를 나타내는 \ ( X \) 접미사 문자열 대표 \ (Y \) 스트링의 최대 길이를 대표.

우리는 단순히 \ (\ mathrm {BFS} \ ) 원래 \ (\ mathrm {트리는} \ ) 나무, 때 노드 \ (X \) 에서 \ (\ mathrm {트리는} \ ) 문자에 존재하는 \ (C \) 전송 측, 우리는 할 때 \ (\ 델타 (X, C) = \ mathrm {트리는} (X, C) \) 및 업데이트 \ (\ mathrm \ {실패} ) 포인터 (\ (\ 델타 \ 실패} {mathrm (X), C) \) , 그리고 반대 부사장, 수행 할 수 있도록 \ (\ 델타 (X, C) = \ 델타 (\ 실패} {mathrm (X), C는) \) , 쉽게 정확도를 알고 .

\ (\ mathrm {아호-Corasick } \) 시간 텍스트 매칭, 복수 패턴이 선택 광포의 기여도를 계산하는 경우, 주목된다 (선형 복잡하다 달성 구성된 정합 마톤 \ (\ mathrm {실패} \) , 다음 시간 복잡도)을 보장 할 수 없습니다.

\ (\ mathrm {크 누스 - 모리스 -Pratt} \) 자동 장치는 하나 개의 문자열 (\ mathrm {아호-Corasick \ } \) 오토마타.

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 20;
struct AhoCorasickautomaton
{
    int trans[N][26],fail[N],end[N],q[N],tot,head,tail;
    inline void insert(char *s,int id)
    {
        int len = strlen( s + 1 ) , now = 0;
        for (int i = 1; i <= len; i++)
        {
            int c = s[i] - 'a';
            if ( !trans[now][c] ) trans[now][c] = ++tot;
            now = trans[now][c];
        }
        end[id] = now;
    }
    inline void build(void)
    {
        head = 1 , tail = 0;
        for (int i = 0; i < 26; i++)
            if ( trans[0][i] ) q[++tail] = trans[0][i];
        while ( head <= tail )
        {
            int x = q[head++];
            for (int i = 0; i < 26; i++)
                if ( !trans[x][i] )
                    trans[x][i] = trans[fail[x]][i];
                else {
                    fail[trans[x][i]] = trans[fail[x]][i];
                    q[++tail] = trans[x][i];
                }
        }
    }
};

시퀀스 자동 장치

모든 서열만을 인식 서열 식별 결정적 유한 상태 오토 마톤.

정의에 의해,이 구성 할 수있다 (| S | +1 \) \ 각 상태 오토 마톤 상태들, 심지어 반대면은 최종 상태로 사용될 수 있고, 시간 복잡도 \ (O (n \ 시그마) \) .

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6;
struct SequenceAutomaton
{
    int trans[N][26],next[26];
    inline void Build(char *s)
    {
        int n = strlen( s + 1 );
        memset( next , 0 , sizeof next );
        for (int i = n; i >= 1; i--)
        {
            next[ s[i] - 'a' ] = i;
            for (int j = 0; j < 26; j++)
                trans[i-1][j] = next[j];
        }
    }
};

최소 표현

문자열 취득 \ (S \) 사이클에서의 모든 것은 전적으로 작은 나타낸다.

두 개의 포인터를 사용할 수 \ (I, J \) 주사, 비교 \ (난, J \) 사이클 두 위치의 시작 동형 문자열에서, 그리고 순차적으로 비교는 길이가 발견 될 때까지 하방으로 격렬한 \ (K를 \) 즉, 이러한 \ (S_ {I + K}> S_ {J + K}는 \) , 우리는 직접 주문 수 \ (I는 I + K +를 =. 1 \) 로 인해 모든 위해 [0에서 \ (p \ K] \ ) , 동형 문자열 \ (S_ {I + P} \) 균질 문자열보다 \ (S_ {J + P} \) 열등하므로 추가적인 비교.

시간의 복잡성이 알고 쉽게 \ (O (N-) \) .

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 20;
int n,s[N<<1];
int main(void)
{
    scanf( "%d" , &n );
    for (int i = 1; i <= n; i++)
        scanf( "%d" , &s[i] ) , s[i+n] = s[i];
    int i = 1 , j = 2 , k;
    while ( i <= n && j <= n )
    {
        for (k = 0; k < n && s[i+k] == s[j+k]; k++);
        if ( k == n ) break;
        if ( s[i+k] > s[j+k] ) ( i += k + 1 ) += ( i == j );
        if ( s[i+k] < s[j+k] ) ( j += k + 1 ) += ( i == j );
    }
    i = min( i , j ) , j = i + n - 1;
    for (int p = i; p <= j; p++) printf( "%d " , s[p] );
    return puts("") , 0;
}

접미사 오토마타

결정적 유한 상태 자동 장치는 모든 접미어를 인식하고 단지 문자열을 인식하고 있습니다.

증분 시공 방법을 참조하십시오 "접미사 자동 장치 항목 SuffixAutomaton을」 .

정적 이동 측, 시간과 공간 복잡성을 이용하여 메모리 어레이 (\ O (N- \ 시그마) \) , 링크 된리스트는 시간 복잡도에 최적화 될 수있다 \ (O (N-) \) . 트리 평형 입금 전송 시간 복잡도 에지 \ (O (N- \ 기록 \ 시그마) \) , 공간 복잡도 \ (O (N-) \) .

struct SuffixAutomaton
{
    int trans[N][26],link[N],maxlen[N],tot,last;
    // trans为转移函数,link为后缀链接,maxlen为状态内的最长后缀长度
    // tot为总结点数,last为终止状态编号
    SuffixAutomaton () { last = tot = 1; } // 初始化:1号节点为S
    inline void Extend(int c)
    {
        int cur = ++tot , p;
        maxlen[cur] = maxlen[last] + 1;
        // 创建节点cur
        for ( p = last; p && !trans[p][c]; p = link[p] ) // 遍历后缀链接路径
            trans[p][c] = cur; // 没有字符c转移边的链接转移边
        if ( p == 0 ) link[cur] = 1; // 情况1
        else {
            int q = trans[p][c];
            if ( maxlen[q] == maxlen[p] + 1 ) link[cur] = q; // 情况2
            else {
                int cl = ++tot; maxlen[cl] = maxlen[p] + 1; // 情况3
                memcpy( trans[cl] , trans[q] , sizeof trans[q] );
                while ( p && trans[p][c] == q )
                    trans[p][c] = cl , p = link[p];
                link[cl] = link[q] , link[q] = link[cur] = cl;
            }
        }
        last = cur;
    }
};

일반화 된 접미사 오토마타

결정 성 유한 상태 오토 마톤은 문자열의 식별 및 수집을 인식 (S \) \ 모두 모든 문자열을 접미사.

생성자 메소드 유사한 좁은 접미사 오토 마톤은 노드는 단순히 이동 측 충돌을 분할 할 수있다.

시간적 복잡도는 같은 접미사 자동 장치입니다.

그것은 기계적으로 일반화 된 접미사 트리 병합 세그먼트를 유지하는 경우 가치가, 그 언급이다 (\ \ mathrm {endpos는을} \ ) 설정을 할 필요가 \ (\ mathrm {DFS} \ ) 이송 \ (\ mathrm {부모를} \ ) 트리 결합, 기수 정렬 순서의 토폴로지에 따라 결합 할 수 없습니다 .

struct SuffixAutomaton
{
    int trans[N][26],link[N],maxlen[N],tot;
    SuffixAutomaton () { tot = 1; }
    inline int Extend(int c,int pre)
    {
        if ( trans[pre][c] == 0 )
        {
            int cur = ++tot , p;
            maxlen[cur] = maxlen[pre] + 1;
            for ( p = pre; p && !trans[p][c]; p = link[p] )
                trans[p][c] = cur;
            if ( p == 0 ) link[cur] = 1;
            else {
                int q = trans[p][c];
                if ( maxlen[q] == maxlen[p] + 1 ) link[cur] = q;
                else {
                    int cl = ++tot; maxlen[cl] = maxlen[p] + 1;
                    memcpy( trans[cl] , trans[q] , sizeof trans[q] );
                    while ( p && trans[p][c] == q )
                        trans[p][c] = cl , p = link[p];
                    link[cl] = link[q] , link[q] = link[cur] = cl;
                }
            }
            return cur;
        }
        else {
            int q = trans[pre][c];
            if ( maxlen[q] == maxlen[pre] + 1 ) return q;
            else {
                int cl = ++tot; maxlen[cl] = maxlen[pre] + 1;
                memcpy( trans[cl] , trans[q] , sizeof trans[q] );
                while ( pre && trans[pre][c] == q )
                    trans[pre][c] = cl , pre = link[pre];
                return link[cl] = link[q] , link[q] = cl;
            }
        }
    }
};

접미사 트리

문자열 \ (S가 \) 모든 접미사에 삽입 \ (\ mathrm {트리는} \ ) 나무, 우리는 나무를 호출 \ (\ mathrm {트리는} \ ) 문자의 가상 나무 모든 잎 노드를 문자열 접미사 트리.

\ (\ mathrm {endpos는이} \ ) 원래 문자열 접미어 기계적 삽입 역방향 쉽다는 점, 등가 클래스 및 속성을 정의 \ (\ mathrm {부모} \ ) 트리 문자열의 접미사 트리이고, 사용될 수있는 접미사 오토마타 생성자 접미사 트리를 추구.

시간 및 복잡성 접미사 기계적 동시에 복잡 할 수있다 (\) O (N) \을 접미사 배열을 전달하고자.

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+20;
struct SuffixAutomaton
{
    int trans[N][26],link[N],maxlen[N],tot,last;
    int id[N],flag[N],trie[N][26],sa[N],rk[N],hei[N],cnt;
    // id 代表这个状态是几号后缀 , flag 代表这个状态是否对应了一个真实存在的后缀
    SuffixAutomaton () { tot = last = 1; }
    inline void Extend(int c,int pos)
    {
        int cur = ++tot , p;
        id[cur] = pos , flag[cur] = true;
        maxlen[cur] = maxlen[last] + 1;
        for ( p = last; p && !trans[p][c]; p = link[p] )
            trans[p][c] = cur;
        if ( p == 0 ) link[cur] = 1;
        else {
            int q = trans[p][c];
            if ( maxlen[q] == maxlen[p] + 1 ) link[cur] = q;
            else {
                int cl = ++tot; maxlen[cl] = maxlen[p] + 1;
                memcpy( trans[cl] , trans[q] , sizeof trans[q] );
                while ( p && trans[p][c] == q )
                    trans[p][c] = cl , p = link[p];
                link[cl] = link[q] , id[cl] = id[q] , link[q] = link[cur] = cl;
            }
        }
        last = cur;
    }
    inline void insert(int x,int y,char c) { trie[x][c-'a'] = y; }
    inline void Build(char *s,int n)
    {
        for (int i = n; i >= 1; i--)
            Extend( s[i]-'a' , i );
        for (int i = 2; i <= tot; i++)
            insert( link[i] , i , s[ id[i] + maxlen[link[i]] ] );
    }
    inline void Dfs(int x)
    {
        if ( flag[x] ) sa[ rk[id[x]] = ++cnt ] = id[x];
        for (int i = 0 , y; i < 26; i++)
            if ( y = trie[x][i] ) Dfs(y);
    }
    inline void Calcheight(char *s,int n)
    {
        for (int i = 1 , k = 0 , j; i <= n; i++)
        {
            if (k) --k; j = sa[ rk[i]-1 ];
            while ( s[ i+k ] == s[ j+k ] ) ++k;
            hei[ rk[i] ] = k;
        }
    }
};
SuffixAutomaton T; char s[N];
int main(void)
{
    scanf( "%s" , s+1 );
    int n = strlen( s+1 );
    T.Build( s , n ) , T.Dfs(1);
    T.Calcheight( s , n );
    for (int i = 1; i <= n; i++)
        printf( "%d%c" , T.sa[i] , " \n"[ i == n ] );
    for (int i = 2; i <= n; i++)
        printf( "%d%c" , T.hei[i] , " \n"[ i == n ] );
    return 0;
}

회문 오토마타

결정 성 유한 상태 오토 마톤 하나를 식별하는 유일한 식별 문자열 \ (S \) 모든 회문 문자열 우측 절반 .

패리티 서브 스트링 회문 때문에, 회문 따라서 홀수 및 짝수 팔린 드롬 서열 팔린 드롬 서열을 나타내는 두 개의 초기 상태 오토 마톤을 갖는다.

당신은 문자열을 증명하기 위해 수학적 귀납법을 사용하여 \을 (\) 최대를 \ (| \ | S) 회문 상태 자동 장치가 회문 문자열을 나타내는 다른 자연 회문 문자열, 그래서. 그리고 각 에지 회문 이송 로봇은 플러스 원래 문자열의 양쪽에, 문자열 남아 따라서 전송 회문 순서는 단지 상동 서열의 상동 자동 장치 오른쪽 절반을 인식 이유를 설명냅니다 .

동일한 구성을 사용하여 오토 마톤 회문 증가 방법. 각 주, 우리는이라는 긴 회문 추가 접미사 대응 상태 기록 (\ mathrm {링크} \ \ ) 기능을. 우리는 문자열을 삽입 끝에서, 우리는 원래 문자열 홉의 마지막 상태에서 시작하면 \ (\ mathrm 링크} {\) , 회문 시퀀스를 구성하고, 새로운 상태를 판단 할 수있다.

새로운 상태를 들어, 당신은 여전히 뛰어 계속할 수 있습니다 \ (\ mathrm {링크} \) , 가장 긴 회문 접미사를 찾을 수 있습니다.

회문 자동 장치 트리도 회문라고, 두 그루의 나무로 볼 수있다. 를 들어 \ (\ mathrm {링크} \ ) 도 나무를 구성하는 포인터, 회문 접미사 트리라고 할 수있다. 정의 에너지 함수 \ (\ 피 (P) \ ) 프로그램 상태 (\ P \) 깊이 팔린 드롬 접미사 트리 알고리즘이 구성에 따르면, 쉽게 알고 \ (\ 피 (P) \ 당량 \ 피 (\ mathrm {링크 } (P)). 1 + \) 와 점프 \ (\ mathrm {링크} \ ) 가능성 함수가 감소된다. 상태 오토 마톤의 개수가 팔린 드롬이기 때문에 (O (N) \) \ 상기 회문 접미사 트리의 최대 깊이는 (N- \) \ , 알고리즘의 시간 복잡도가 초과하지 않도록 구성 될 수있다 O ((\ N- 형) \) .

공간 복잡도는 \ (O (N- \ 시그마) \) 의 인접리스트 저장 측을 이용하여, 시간 복잡도 제기 \ (O (N- \ 시그마) \) , 공간적 복잡도가 감소되는 ) \ (O (n \ ) . 만약 \ (\ mathrm {해시} \ ) 테이블 금고 측, 시간과 공간 복잡성을 감소하는 \ (O (N-) \) .

회문의 긴 문자열은 접미사해야 회문 때문에 {\ (\ mathrm 국경} \) 그래서 상동 나무, (\ \ \ mathrm {DP} ) 사용할 수 있습니다 \ mathrm {국경 \ 시리즈 (\ } \) 산술 특성. 두 개의 추가 파라미터를 기록한다 회문 마톤 \ (\ mathrm {DIF} \ ) 와 (\ mathrm 스 링크 {} \) \ \ (\ mathrm DIF} {(X) = \ mathrm LEN {} (X를 ) - \ mathrm 렌 {} (\ mathrm {링크} (X)) \) , \ (\ mathrm 스 링크} {(X) \) 를 기록 회문 접미사 트리 \ (X \) 깊은 조상, 만족 \ (\ mathrm {} DIF (\ mathrm 스 링크} {(X)) \ = 없음 \ mathrm의 DIF} {(X) \) , 건설 지나가는 동안 유지 될 수있다.

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20 , Mod = 1e9 + 7;
struct PalindromesAutomaton
{
    int n,tot,last,link[N],slink[N],trans[N][26],len[N],dif[N],s[N];
    PalindromesAutomaton(void)
    {
        len[ last = 0 ] = 0 , link[0] = 1;
        len[1] = -1 , tot = 1 , s[0] = -1;
    }
    inline void Extend(int c)
    {
        int p = last; s[++n] = c;
        while ( s[n] != s[ n - len[p] - 1 ] ) p = link[p];
        if ( trans[p][c] == 0 )
        {
            int cur = ++tot , q = link[p];
            len[cur] = len[p] + 2;
            while ( s[n] != s[ n - len[q] - 1 ] ) q = link[q];
            link[cur] = trans[q][c] , trans[p][c] = cur;
            dif[cur] = len[cur] - len[ link[cur] ];
            if ( dif[cur] != dif[ link[cur] ] ) slink[cur] = link[cur];
            else slink[cur] = slink[ link[cur] ];
        }
        last = trans[p][c];
    }
};

추천

출처www.cnblogs.com/Parsnip/p/12369642.html