「共通の文字列アルゴリズムの概要」

一般的な文字列のアルゴリズムの概要

文字列のハッシュ

典型的には、多項式使用\(\ mathrm {ハッシュ} \ ) メソッドエンパワーメント、正の整数に文字列をマッピングします。

\ [F(S)= \ sum_ ^ {I 1 =} S_I | | {| | S} \回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)\のLeq \ピー(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 \)を定義、\(\ mathrm {KMP} \ ) :オートマトンが成立

\(1 \)の状態の数\(N + 1 \)
\(2 \)すべての接頭辞を特定します。
\(3 \) の伝達関数(\ \デルタ(P、C )\) 状態\(P \)接続文字プレフィックスに対応する\(C \)最長後\(\ mathrm {ボーダー} \ ) 位置状態を、対応する接頭辞

コンストラクタメソッド\(\ mathrm {クヌース-モリス -Pratt} \) アルゴリズム同様に、時間複雑である\(O(N \ Sigma社)\)

#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 {BFSを} \ ) 転送シーケンスコンストラクタ関数\(\デルタ\)

我々は、各状態が有する定義\(\ 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} \) オートマトンは、1つの文字列である(\ 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 \ Sigma)を \) 。

#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 \)スキャン、比較\(I、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 \ Sigma社)\)を、リンクされたリストは、時間複雑さのために最適化することができる\(O(N)\) ツリーが平衡預金転送、時間複雑さとエッジ\(O(N \ログ\ Sigma社)\) 空間複雑\(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;
}

回文オートマトン

決定性有限オートマトン、1つを識別し、唯一の識別文字列\(S \)すべての回文の文字列の右半分

パリティ部分文字列のパリンドロームので、パリンドロームは、このように奇数と偶数のパリンドローム配列パリンドローム配列を表す2つの初期状態オートマトンを有しています。

あなたは、文字列を証明するために数学的帰納法を使用することができます\(S \)高々を(\ | | S)\回文状態オートマトンは回文の文字列を表しますので、異なる性質の回文文字列を。そして、各エッジパリンドローム搬送ロボットはプラス元の文字列の両側に、文字列の遺跡は、このように転送さパリンドローム配列はまた、唯一の回文配列の回文構造オートマトンの右半分を認める理由を説明表し。

同じ構成を使用して、パリンドロームオートマトン増分方法。各状態のため、我々は、と呼ばれる最も長いパリンドローム追加サフィックス対応する状態記録(\ mathrm {リンク} \ \ ) 機能。終わりに、我々は、文字列を挿入すると、我々は、元の文字列ホップの最終状態から開始(\ mathrmリンク} {\)\、パリンドローム配列を構成し、新しい状態を判定してもよいです。

新しい状態のために、あなたはまだジャンプし続けることができます\(\ mathrm {リンク} \) 最長の回文サフィックスを見つけます。

回文オートマトンは木も回文と呼ばれ、2つのツリーとして見ることができます。以下のための\(\ mathrm {リンク} \ ) 、ツリーを構成するポインタは、パリンドローム接尾辞木と呼ぶことができます。定義されたポテンシャルエネルギー関数\(\ファイ(P)\ ) 示す状態\(P \)深さのパリンドローム接尾辞木は、アルゴリズムが構成によれば、簡単に知ることが\(\ファイ(P)\当量\ピー(\ mathrm {リンク}(P))。1 + \) およびジャンプ\(\ mathrm {リンク} \ ) ポテンシャル関数が小さくなります。オートマトンの数はパリンドロームであるので、そして(O(N)\)\、パリンドローム接尾辞木の最大深さが(N \)\、アルゴリズムの時間複雑さを超えないように構成されてもよいO((\ N-)\)

空間的複雑さは、\(O(N \ Sigma社)\) 隣接リスト記憶側を使用して、時間複雑度が上昇\(O(N \ Sigma社)\) 空間的複雑さが低減される)\(O(N \ )もし\(\ mathrm {ハッシュ} \ ) 表預金側は、時間と空間の複雑さを低減している\(O(N)\)

パリンドロームの最長文字列は、サフィックスでなければならないので、パリンドローム{\(\ mathrmボーダー} \)ので回文木、(\ \ \ mathrm {DP} )を使用することができる\ mathrm {ボーダー\シリーズ(\ } \)の演算特性。パリンドロームオートマトンは、2つの追加パラメータを記録する\(\ mathrm {DIFを} \ ) と\(\ mathrm SLINK} {\)、\ (\ mathrm DIF} {(X)= \ mathrm LEN {}(X ) - \ mathrm LEN {}(\ mathrm {リンク}(X))\)、\ (\ mathrm SLINK} {(X)\)記録パリンドローム接尾辞木\(X \)最深祖先、満足\(\ mathrm {DIF}(\ mathrm SLINK} {(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