字符串相关
补一些字符串姿势。
一: 最小表示法 {
初始时,让i=0,j=1,k=0,其中i,j,k表示的是以i开头和以j开头的字符串的前k个字符相同
分为三种情况
1.如果str[i+k]==str[j+k] k++。
2.如果str[i+k] > str[j+k] i = i + k + 1,即最小表示不可能以str[i->i+k]开头。
3.如果str[i+k] < str[j+k] j = j + k + 1,即最小表示不可能以str[j->j+k]开头。
那么只要循环n次,就能够判断出字符串的最小表示是以哪个字符开头。
1 int getMin(){ 2 int n=strlen(S); 3 int i=0,j=1,k=0; 4 while (i<n&&j<n&&k<n){ 5 int t=S[(i+k)%n]-S[(j+k)%n]; 6 if (t==0) k++; 7 else{ 8 if (t>0) i+=k+1; 9 else j+=k+1; 10 if (i==j) j++; 11 k=0; 12 } 13 } 14 return i<j?i:j; 15 }
}
二:KMP {
1 void getkmp(char S[]){ 2 int n=strlen(S+1); 3 nex[0]=nex[1]=0; 4 for (int i=2,j;i<=n;++i){ 5 for (j=nex[i-1];j&&S[i]!=S[j+1];j=nex[j]); 6 if (S[i]!=S[j+1]) nex[i]=0; else nex[i]=j+1; 7 } 8 }
}
三:AC自动机 {
1 //rt is 0 2 l=r=1; 3 for (int i=0;i<26;++i) if (ch[0][i]) nex[d[r++]=ch[0][i]]=0; 4 while (l<r){ 5 for (int i=0;i<26;++i) 6 if (ch[d[l]][i]){ 7 d[r++]=ch[d[l]][i]; 8 nex[ch[d[l]][i]]=ch[nex[d[l]]][i]; 9 }else ch[d[l]][i]=ch[nex[d[l]]][i]; 10 ++l; 11 }
}
四:后缀自动机(SAM) {
转到http://www.cnblogs.com/cyz666/p/6527196.html
}
五:后缀数组(SA) {
1 #include <bits/stdc++.H> 2 using namespace std; 3 int n,m,F0[27],rk[200005],sa[200005],H[200005],g1[200005],g2[200005],nex[200005]; 4 char S[200005]; 5 int main(){ 6 scanf("%s",S+1); n=strlen(S+1); 7 for (int i=1;i<=n;++i) ++F0[S[i]-'a'+1]; 8 for (int i=2;i<=26;++i) F0[i]+=F0[i-1]; 9 for (int i=1;i<=n;++i) rk[i]=F0[S[i]-'a'+1]; 10 for (int m=1,t=F0[26];m<n;m<<=1){ 11 for (int i=1;i<=n;++i){ 12 nex[i]=g1[rk[i+m]]; 13 g1[rk[i+m]]=i; 14 } 15 for (int i=t;i>=0;--i){ 16 for (int j=g1[i],k;j;j=k){ 17 k=nex[j]; nex[j]=g2[rk[j]]; g2[rk[j]]=j; 18 } 19 g1[i]=0; 20 } 21 int z=0,y; 22 for (int i=1;i<=t;++i){ 23 y=-1; 24 for (int j=g2[i],k;j;j=k){ 25 k=nex[j]; nex[j]=0; 26 if (y!=rk[j+m]) y=rk[j+m],++z; 27 H[j]=z; 28 } 29 g2[i]=0; 30 } 31 t=z; 32 for (int i=1;i<=n;++i) rk[i]=H[i]; 33 } 34 for (int i=1;i<=n;++i) sa[rk[i]]=i,H[i]=0; 35 for (int i=1,k=0;i<=n;++i) 36 if (rk[i]!=1){ 37 if (k) --k; 38 while (S[i+k]==S[sa[rk[i]-1]+k]) ++k; 39 H[rk[i]]=k; 40 } 41 /*for (int i=1;i<=n;++i){ 42 printf("%d ",H[i]); 43 for (int j=sa[i];j<=n;++j) putchar(S[j]); puts(""); 44 }*/ 45 }
}
六:Manacher {
1 #include <bits/stdc++.h> 2 using namespace std; 3 char S[200005]; 4 int n,x,a[400005],f[400005]; 5 int main(){ 6 scanf("%s",S+1); n=strlen(S+1); 7 for (int i=1;i<=n;++i) a[i*2]=S[i]-'a'+1; 8 x=0; 9 for (int i=1,j;i<=n*2+1;++i){ 10 if (i<=x+f[x]) j=min(f[x-i+x],x+f[x]-i); else j=0; 11 for (++j;i+j<=n*2-1&&i-j>0&&a[i+j]==a[i-j];++j); 12 f[i]=j-1; 13 if (f[i]+i>f[x]+x) x=i; 14 } 15 for (int i=1;i<=n*2+1;++i) printf("%d ",a[i]); puts(""); 16 for (int i=1;i<=n*2+1;++i) printf("%d ",f[i]); puts(""); 17 return 0; 18 }
}
七:回文自动机 {
}
八:一些结论
1.border。
串A,串B 若串S=AB=BA则,可以得出gcd(|A|,|B|)是S的一个循环节。
由此可得引理,对S做kmp后, 若n-nex[n]整除n则 串S是最小循环节为n-nex[n] 否则 串S不是循环串
2.候选最小后缀 (这里的最小指字典序,空集字典序最小)。
定义:串S的一个后缀A是候选最小后缀(以下简称"小后缀" 与最小后缀区分) ,则存在字符串T使得 AT是ST的最小后缀
如aaba中 aaba和a 是小后缀。
可以证明,两个小后缀A,B (|A|<|B|) 一定满足A是B前缀,且|A|*2<=|B|。 因此可以证明 串S最多有log(len)个小后缀。
所以可以用线段树维护区间字符串的某些最小字典序问题。。因为 最多只要维护区间的log个。
3.回文串
回文串S的后缀T是回文串,当且仅当T是S的border。
回文串的所有border都是回文串
4.循环同构
串S与T循环同构,当且仅当S是TT的子串。(S,T长度当然要相同)
若A,B都是自身的循环同构中字典序最小,且A<B,则AB是循环同构最小的
5.周期
串S有两个周期p,q。 gcd(p,q)不一定是串S的周期,反例:abaaba。
Periodicity Lemma:当且仅当p+q-gcd(p,q)<=|S| 时, gcd(p,q)也是串S的周期
6.匹配与等差数列
若2|u|>=|v|,则串u在v中的所有匹配构成等差数列。
若匹配的个数>=3个,则公差为u的最小周期。
(若只有2个 则不一定是最小的周期, 比如:u=aabaa , v=aabaaabaa)
7.border与等差数列
将S的所有border按长度 分成 [1,2),[2,4),[4,8),...,[2^(i-1) 2^i) , [2^i n)
log组,则每一组是一个等差数列。