后缀自动机学习
后缀自动机就是能识别一个串的所有后缀,附加功能是可以识别所有字串,还能统计字串个数。
后缀自动机的parent是指该节点的父亲,len表示这个节点能表示的最长字串的长度
child[][]表示该节点添加一个字符能到达的状态。。
spoj1811 求两个串的最长公共字串
解法:用a串建立后缀自动机,然后用b串在自动机上跑,其中parent数组记录节点的父亲,len记录该节点能表示的最大字串长度
定义长度为num,如果遇到字符x存在孩子child[u]那么num++,否则找父亲节点,直到找到有child[u]的节点
同时令num=len[u]+1(父亲节点的len值一定是跟从根到该节点长度一致的的)
如果失配了,也就是访问到根,那么num=0,不断记录num的最大值即可
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define maxn 510000 struct Auto_Sufix{ int parent[maxn]; int child[maxn][26]; int len[maxn]; int last; int cnt; int newNode(){ memset(child[cnt],0,sizeof(child[cnt])); len[cnt] = parent[cnt] = 0; return cnt++; } void init(){ last = cnt = 1; newNode(); } void add(int x){ int np = newNode(),pa = last; len[np] = len[last]+1; while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa]; if(pa == 0) parent[np] = 1; else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x]; else { int nq = newNode(),p=child[pa][x]; memcpy(child[nq],child[p],sizeof(child[p])); len [nq] = len[pa]+1; parent[nq] = parent[p]; parent[np] = parent[p] = nq; for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]); } last = np; } int getmore(){ int pa = parent[last]; return len[last]-len[pa]; } int getMaxCommon(char*word){ int len1 = strlen(word); int ans = 0,num=0,u=1,x; for(int i = 0;i < len1; i++){ x = word[i]-'a'; if(child[u][x] != 0 ){ num++,u=child[u][x]; } else { while(u&&!child[u][x]) u=parent[u]; if(u==0)u=1,num=0; else num=len[u]+1,u=child[u][x]; } ans=max(ans,num); } return ans; } }; Auto_Sufix sufix; char word[maxn]; int main(){ while(scanf("%s",word)!=EOF){ sufix.init(); int len = strlen(word); for(int i = 0;i < len ;i++) sufix.add(word[i]-'a'); scanf("%s",word); int ans= sufix.getMaxCommon(word); printf("%d\n",ans); } return 0; }spoj1812 求多个串的最长公共字串
跟上一题类似,先建立一个自动机,然后用其他串在上面跑
用ans数组记录当前串到每个状态得到的最大长度,用原来的自动机的len记录所有串到达这些状态的公共长度的最大值
但是在跟新len值的时候需要注意:因为一个状态能表示的长度大于其父亲状态的长度,如果到达这个状态了,说明其
父亲状态也是可达,同时父亲的父亲。。。也是可达的。所以还需要跟新父亲状态的ans值
因为后缀自动机的节点是一个拓扑图,没有环路的有向图,根据定义len越大的状态能表示的字串越多,其父亲的状态的len
必然小于他的len值,对这些状态的len值拍个序,就能实现逆拓扑序了,采用基数排序,可以做到o(n)的排序
然后用len值最大的节点跟新len,在用这个节点跟新该父亲的ans即可
top[i]记录的就是len值排在i位的节点的下标
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define maxn 210000 struct Auto_Sufix{ int parent[maxn]; int child[maxn][26]; int len[maxn]; int last; int cnt; int newNode(){ memset(child[cnt],0,sizeof(child[cnt])); len[cnt] = parent[cnt] = 0; return cnt++; } void init(){ last = cnt = 1; newNode(); } void add(int x){ int np = newNode(),pa = last; len[np] = len[last]+1; while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa]; if(pa == 0) parent[np] = 1; else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x]; else { int nq = newNode(),p=child[pa][x]; memcpy(child[nq],child[p],sizeof(child[p])); len [nq] = len[pa]+1; parent[nq] = parent[p]; parent[np] = parent[p] = nq; for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]); } last = np; } int getmore(){ int pa = parent[last]; return len[last]-len[pa]; } }; Auto_Sufix sufix; char word[maxn]; int ans[maxn]; int top[maxn]; int main(){ gets(word); sufix.init(); int len = strlen(word); for(int i = 0;i < len ;i++) sufix.add(word[i]-'a'); memset(ans,0,sizeof(ans)); int t = 0; for(int i = 0;i < sufix.cnt;i++) ans[i]=0; for(int i = 0;i < sufix.cnt;i++) ans[sufix.len[i]]++; for(int i = 1;i < sufix.cnt;i++) ans[i] += ans[i-1]; for(int i = sufix.cnt-1;i>0;i--) top[--ans[sufix.len[i]]]=i; memset(ans,0,sizeof(ans)); while(gets(word)){ int len1 = strlen(word); int num=0,u=1,x; for(int i = 0;i < len1; i++){ x = word[i]-'a'; if(sufix.child[u][x] != 0 ){ num++,u=sufix.child[u][x]; } else { while(u&&!sufix.child[u][x]) u=sufix.parent[u]; if(u==0)u=1,num=0; else num=sufix.len[u]+1,u=sufix.child[u][x]; } ans[u] = max(ans[u],num); } for(int i = sufix.cnt-1;i>1;i--){ u = top[i]; sufix.len[u] = min(sufix.len[u],ans[u]); ans[sufix.parent[u]] = max(ans[sufix.parent[u]],ans[u]); ans[u] = 0; } } int res = 0 ; for(int i = 2;i < sufix.cnt;i++) res = max(res,sufix.len[i]); printf("%d\n",res); return 0; }
hdu 4416Good Article Good sentence
求一个字符串A 的所有不重复字串的个数,同时这些子串不出现在s1,s2,.....sn中
同样用A建立一个自动机,然后开一个len1数组,记录的是s1到sn在这个自动机上跑能得到字串长度的最大值
同样用逆拓扑序遍历最后生成的len1数组,且需要跟新每个节点的父亲节点。记录len1[u]-len[parenrt[u]]]表示到达的这个状态最多可以表示
几个字串,可能为负数,因为不可达,那么取0和该值得最大值。
父亲节点的值为len1[u]和len[paren[u]]的最小值,防止重复计算
然后用a串的字串数-重复的字串数就是答案,可能超int
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 200001 int parent[maxn],child[maxn][26],len[maxn],len1[maxn]; int cnt,last; int newNode(){ memset(child[cnt],0,sizeof(child[cnt])); len[cnt] = parent[cnt] = 0; return cnt++; } void init(){ cnt = last = 1; newNode(); } void add(int x){ int pa = last, np = newNode(); len[np] = len[pa]+1; last = np; while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa]; if(pa == 0) parent[np] = 1; else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x]; else { int nq = newNode(), p = child[pa][x]; memcpy(child[nq],child[p],sizeof(child[nq])); parent[nq] = parent[p]; len[nq] = len[pa]+1; parent[p] = parent[np] = nq; while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa]; } } #define ll long long ll getmore(){ ll ans = 0; for(int i = 2;i < cnt;i++) ans += len[i]-len[parent[i]]; return ans; } char word[maxn]; int bucket[maxn],top[maxn]; int main(){ int t,n,lenth; scanf("%d",&t); for(int tt = 1; tt <= t; tt++){ scanf("%d\n",&n); gets(word); lenth = strlen(word); init(); for(int i = 0;i < lenth; i++) add(word[i]-'a'); ll ans = getmore(); for(int i = 0;i < cnt; i++)len1[i] = 0; while(n--){ gets(word); lenth = strlen(word); int u=1,x,sum=0; for(int i = 0;i < lenth; i++){ x = word[i]-'a'; if(child[u][x] != 0) u = child[u][x],sum++; else { while(u&&child[u][x]==0)u=parent[u]; if(u == 0) u=1,sum=0; else sum=len[u]+1,u=child[u][x]; } len1[u] = max(sum,len1[u]); } } for(int i = 0;i < cnt;i++)bucket[i] = 0; for(int i = 0;i < cnt;i++)bucket[len[i]]++; for(int i = 1;i < cnt;i++)bucket[i]+=bucket[i-1]; for(int i = cnt-1;i >=0;i--)top[--bucket[len[i]]]=i; long long res = 0; int u,n; for(int i = cnt-1;i > 1;i--){ u = top[i]; // if(len1[u] == 0) res+=len[u]-len[parent[u]]; // else if(len1[u]<len[u])res+=len[u]-len1[u]; // len1[parent[u]] = max(len1[parent[u]],len1[u]); n = max(0,len1[u]-len[parent[u]]); res+=n; if(parent[u])len1[parent[u]] = min(len[parent[u]],max(len1[u],len1[parent[u]])); } printf("Case %d: %I64d\n",tt,ans-res); } return 0; }
hdu 4622Reincarnation多校题
给一个字符串,给一些询问求在某一个区间字串个数
后缀自动机枚举不同起点,ans[i][j]表示从i到j的字串个数
然后在线回答即可。离线回答可能快一些,可以减去一些不必要的起点
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define maxn 4007 struct Auto_Sufix{ int parent[maxn]; int child[maxn][26]; int len[maxn]; int last; int cnt; int newNode(){ memset(child[cnt],0,sizeof(child[cnt])); len[cnt] = parent[cnt] = 0; return cnt++; } void init(){ last = cnt = 1; newNode(); } void add(int x){ int np = newNode(),pa = last; len[np] = len[last]+1; last = np; while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa]; if(pa == 0) parent[np] = 1; else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x]; else { int nq = newNode(),p=child[pa][x]; memcpy(child[nq],child[p],sizeof(child[p])); len [nq] = len[pa]+1; parent[nq] = parent[p]; parent[np] = parent[p] = nq; for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]); } } int getmore(){ int pa = parent[last]; return len[last]-len[pa]; } }; Auto_Sufix sufix; char word[maxn]; int ans[maxn][maxn]; int main(){ int t; scanf("%d",&t); while(t--){ scanf("%s",word); memset(ans,0,sizeof(ans)); int n = strlen(word); for(int i = 0;i < n; i++){ sufix.init(); for(int j = i;j < n; j++){ sufix.add(word[j]-'a'); ans[i][j] = ans[i][j-1]+sufix.getmore(); } } int q,b,t; scanf("%d",&q); while(q--){ scanf("%d%d",&b,&t); printf("%d\n",ans[b-1][t-1]); } } return 0; }
hdu 4436 str2int
给一些数字字符串,求这些字符串的字串(不重复的,没有前缀0)的和。
将这些字符串用一个连接符连接起来,然后建立后缀自动机
然后按照len值的大小排序
因为不能有前缀0,所以根节点不能访问0的孩子节点
用sum[u]标记u这个所有能到这个点的数值之和,num[u]表示有多少个路径到这里
那么sum[u]就是以u结束的数的和然后转移的时候,sum[u]*10+num[u]*j j表示后面接的数字
所有合法的sum[u]之和就是答案
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define maxn 200007 #define mod 2012 int parent[maxn],child[maxn][11],len[maxn]; int cnt,last; int newNode(){ memset(child[cnt],0,sizeof(child[cnt])); len[cnt] = parent[cnt] = 0; return cnt++; } void init(){ cnt = last = 1; newNode(); } void add(int x){ int pa = last, np = newNode(); len[np] = len[pa]+1; last = np; while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa]; if(pa == 0) parent[np] = 1; else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x]; else { int nq = newNode(), p = child[pa][x]; memcpy(child[nq],child[p],sizeof(child[nq])); parent[nq] = parent[p]; len[nq] = len[pa]+1; parent[p] = parent[np] = nq; while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa]; } } char word[maxn]; int bucket[maxn],top[maxn]; int num[maxn],sum[maxn]; int main(){ int t; while(scanf("%d",&t)!=EOF){ int len1 = 0; for(int i = 0;i < t;i++){ scanf("%s",&word[len1]); len1 += strlen(&word[len1]); word[len1++]='0'+10; } init(); for(int i = 0;i < len1;i++) add(word[i]-'0'); memset(bucket,0,sizeof(bucket)); for(int i = 1;i < cnt; i++)bucket[len[i]]++; for(int i = 1;i < cnt; i++)bucket[i]+=bucket[i-1]; for(int i = cnt-1;i > 0;i--)top[--bucket[len[i]]]=i; memset(num,0,sizeof(num)); memset(sum,0,sizeof(sum)); num[1]=1; int ans = 0,v,res,u,j; for(int i = 0;i<cnt-1;i++){ u = top[i]; for(j = 0;j < 10;j++){ if(j == 0 && u == 1) continue; if(child[u][j]==0)continue; v = child[u][j]; num[v]+=num[u]; if(num[v] >= mod)num[v]-=mod; res = sum[u]*10 + num[u]*j; sum[v]+=res; sum[v]%=mod; } ans+=sum[u]; if(ans >= mod)ans-=mod; } ans%=mod; printf("%d\n",ans); } return 0; }