字符串板子
上板子
KMP:
for(int i=1;i<n;i++){ while(u>0&&s[u]!=s[i]){ u=Pi[u-1]; } if(s[u]==s[i]){ u++; } Pi[i]=u; }
Z Algorithm:
for(int i=1,l=0,r=0;i<len;i++){ if(i<=r)Z[i]=min(r-i+1,Z[i-l]); else Z[i]=0; while(s1[Z[i]]==s1[i+Z[i]])Z[i]++; if(i+Z[i]>r){ for(int j=r+1;j<i+Z[i];j++)Pi[j]=j-i+1; r=i+Z[i]-1,l=i; } }
Z函数与KMP的各种用法:
//查找子串t在s中出现的位置 int u = 0; for(int i=1;i<m;i++){ while(u>0&&t[u]!=t[i])u=Pi[u]; if(t[u]==t[i])u++; Pi[i]=u; } u=0; for( int i = 0;i < n; i++){ while(u>0&&t[u]!=s[i])u=Pi[u]; if(t[u]==s[i])u++; if(u==m){ printf("%d\n",i-m+1); u=Pi[u]; } } //查找每个前缀的出现次数 KMP(s,n,Pi); for (int i = 0; i < n; i++) ans[pi[i]]++; for (int i = n - 1; i > 0; i--) ans[pi[i - 1]] += ans[i]; for (int i = 0; i <= n; i++) ans[i]++; //查找本质不同子串数目 //注意 KMP的查找本质不同子串是O(n方)的 不如后缀数组 for(int i=0;i<n;i++){ char c;int MAX=0; cin>>c; strcat(s,c);s1=s; reverse(s1,s1+i+1); KMP(s1,i+1,Pi); for(int j=0;j<=i;j++)MAX=max(MAX,s1[j]); ans+=(i-MAX); } //查找b与a的每一个后缀的最长公共前缀 scanf("%s%s",s2,s1); int len1=strlen(s1); int len2=strlen(s2); strcat(s1,"#"); strcat(s1,s2); int len=len1+len2,l=0,r=0; for(int i=1;i<=len;i++){ if(i<=r)z[i]=min(r-i+1,z[i-l]); else z[i]=0; while(s1[z[i]]==s1[i+z[i]])z[i]++; if(i+z[i]>r)r=i+z[i]-1,l=i; } for(int i=0;i<len1;i++){ if(!i)printf("%d ",len1); else printf("%d ",z[i]); } puts(""); for(int i=len1+1;i<=len;i++){ printf("%d ",z[i]); } //字符串压缩 Z Algorithm(s,n,Z); for(int i=1;i<n;i++){ if((n%i==0)&&(z[i]+i==n)){ printf("%d ",i); } }//Z puts(""); KMP(s1,n,Pi); printf("%d",n-Pi[n-1]);//KMP
后缀数组:
void Qsort(){//基数排序 for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; } void get_SA(){//得到后缀数组 sa[i](排名第i小的后缀) for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i; Qsort(); for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; Qsort(); swap(x,y); x[sa[1]]=1,num=1; for(int i=2;i<=n;i++){ if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]){ x[sa[i]]=num; }else{ x[sa[i]]=++num; } } if(num==n)break; m=num; } for(int i=1;i<=n;i++){ printf("%d ",sa[i]); } } //lcp(x,y):字符串x与字符串y的最长公共前缀,在这里指x号后缀与与y号后缀的最长公共前缀 void get_height(){//得到height数组 height[i] ( lcp(sa[i],sa[i-1]) ,即排名为i的后缀与排名为i-1的后缀的最长公共前缀) int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++){ if(rk[i]==1)continue; if(k)k--; int j=sa[rk[i]-1]; while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } for(int i=1;i<=n;i++){ printf("%d ",height[i]); } } /* 一些height的用法 1. 两个后缀的最大公共前缀 : lcp(x,y)=min(height[i]),i∈[x,y],用st表维护,O(1)查询 2.可重叠最长重复字串 :height数组里的最大值 3.不可重叠最长重复子串 : 二分答案x maxsa[i],minsa[i],枚举排名第i的后缀 若height[i]>=x 更新maxsa[i]与minsa[i]若(max-min)>=x就return 1 4.本质不同的子串数量 : 枚举每一个后缀,第i个后缀对答案的贡献为len-sa[i]+1-height[i] */ SA
自动机(们):
AC自动机:
void insert(char *s,int id){//trie树的插入 int now=0; for(int i=0;s[i];i++){ if(!trie[now][s[i]-'a'])trie[now][s[i]-'a']=++tot; now=trie[now][s[i]-'a']; } match[id]=now; } void build(){//建trie图 个人理解 trie图=trie树+fail边 for(int i=0;i<26;i++){ if(trie[0][i]){ q.push(trie[0][i]); } } while(!q.empty()){ int from=q.front(); q.pop(); for(int i=0;i<26;i++){ if(trie[from][i]){ fail[trie[from][i]]=trie[fail[from]][i]; q.push(trie[from][i]); }else{ trie[from][i]=trie[fail[from]][i]; } } } } void solve(){//查找t在s中出现次数 scanf("%s", s); for(int i=0,now=0;s[i];i++){//记录路径 now=trie[now][s[i]-'a']; siz[now]++; } for(int i=1;i<=tot;i++){//建fail树 add(fail[i],i); } dfs(0);//求出每个t串在Trie树上的终止节点在fail树上的子树总匹配次数 for(int i=1;i<=n;i++){ printf("%d\n",siz[match[i]]); } }
回文自动机:
int get_fail(char *s,int X,int pos){//得到fail(小于自己长度的最长回文后缀) while(pos-len[X]-1<0||s[pos-len[X]-1]!=s[pos]){ X=fail[X]; } return X; } int get_tran(char *s,int X,int pos,int tot){//得到tran while(pos-len[X]-1<0||s[pos-len[X]-1]!=s[pos]||((len[X]+2)*2>len[tot]))X=fail[X]; return X; } void PAM(char *s){//建回文自动机 len[0]=0,len[1]=-1; fail[0]=1,fail[1]=0; for(int i=0;s[i];i++){ int cur=get_fail(s,last,i); if(!ch[cur][s[i]-'a']){//新的回文子串不在自动机中 fail[++tot]=ch[get_fail(s,fail[cur],i)][s[i]-'a'];//建新节点 先连fail边 len[tot]=len[cur]+2;//更新节点的长度 sum[tot]=sum[fail[tot]]+1;//更新与新的回文子串结尾相同的回文子串个数(即题中要求的) ch[cur][s[i]-'a']=tot;//最后连边(防止找fail边时找到自己) //求tran函数 (小于等于当前节点长度的一半最长回文后缀)(求法跟fail差不多) if(len[tot]<=2)tran[tot]=fail[tot]; else{ tran[tot]=ch[get_tran(s,tran[cur],i,tot)][s[i]-'a']; } } last=ch[cur][s[i]-'a']; } }
后缀自动机(待补)