일반적인 문자열 알고리즘 요약
문자열 해쉬
일반적으로 다항식 사용 \ (\ 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];
}
};