一般的な文字列のアルゴリズムの概要
文字列のハッシュ
典型的には、多項式使用\(\ 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];
}
};