如果不算pre指针的话后缀自动机就是一个DAG,这是它能很方便地进行dp的前提。
而pre指针返回什么呢,返回的就是上一个的前缀包含改结点所代表子串的那个后缀,和AC自动机上的fail指针很像,都是为了匹配。我目前学得不深,看不出和AC自动机的fail指针有什么区别,用起来也几乎一样。
相比于字典树和回文树,后缀自动机每个结点会有多个父结点,可以表示多种子串(从根节点到它的每条路径都是一个子串),因此子串的信息只能在路径中记录,比如长度,而该子串说记录的长度step,则是根结点到它的最远距离,而某个子串的长度就是该代表该子串的路径的长度了,并不一定是某个结点的step。
spoj1811
求两个串的最长公共子串的长度。
对A串建立后缀自动机,对B串进行匹配,如果匹配失败,沿着失败指针往回走到第一个能匹配的位置继续匹配(看起来似曾相识?没错,这不是AC自动机的过程吗。。。),当然如果到根节点还不能继续匹配,那就只有从头再来了。这里随时记录长度更新答案即可。
当然后缀数组也可以做,把两个串拼接起来,求lcp即可。。。然后后缀自动机好快。。。。在spoj上居然60ms过了。。。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=2000100; const int INF=1e9+10; struct SAM { int ch[maxn][26]; int pre[maxn],step[maxn]; int last,tot; void init() { last=tot=0; memset(ch[0],-1,sizeof(ch[0])); pre[0]=-1; step[0]=0; } void add(int c) { c-='a'; int p=last,np=++tot; step[np]=step[p]+1; memset(ch[np],-1,sizeof(ch[np])); while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p]; if(p==-1) pre[np]=0; else{ int q=ch[p][c]; if(step[q]!=step[p]+1){ int nq=++tot; step[nq]=step[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); pre[nq]=pre[q]; pre[q]=pre[np]=nq; while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p]; } else pre[np]=q; } last=np; } int find(char *s) { int len=strlen(s); int res=0,tmp=0; int u=0; REP(i,0,len-1){ int c=s[i]-'a'; if(~ch[u][c]) tmp++,u=ch[u][c]; else{ while(~u&&ch[u][c]==-1) u=pre[u]; if(~u) tmp=step[u]+1,u=ch[u][c]; else tmp=0,u=0; } res=max(res,tmp); } return res; } };SAM sam; char s[maxn],t[maxn]; void solve() { sam.init(); int len=strlen(s); REP(i,0,len-1) sam.add(s[i]); printf("%d ",sam.find(t)); } int main() { freopen("in.txt","r",stdin); while(~scanf("%s%s",s,t)){ solve(); } return 0; }
spoj1812
求多个串的最长公共子串的长度。
对第一个串建立SAM,每个结点多维护两个信息:当前串在该结点匹配到的最长长度Max,多个串在该结点匹配到的最短长度Min,然后逐个放到SAM去更新这些信息就可以了,最后的结果显然是所有结点Min的最大值。
然后需要注意的一点是(这一点也加深了我对后缀自动机的理解),这里和AC自动机的fail指针不一样的是这个比AC自动机具有拓扑关系,是纯粹的DAG,更像回文树,因此匹配更新的时候就不要像AC自动机那样匹配到一个就找沿着fail指针往回走了,因为这样在SAM中是n^2的复杂度,但是可以像回文树计算cnt一样处理,匹配完之后逆序遍历一遍,用每个点更新它的pre结点。
当然后缀数组也可以做,把串都拼接起来,然后二分长度在height数组上滑动窗口检验是否包含n个即可。。。然后后缀自动机真的好快。。。150ms。。。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=1e9+10; struct SAM { int ch[maxn][26]; int pre[maxn],step[maxn]; int last,tot; int Min[maxn],Max[maxn]; void init() { last=tot=0; memset(ch[0],-1,sizeof(ch[0])); pre[0]=-1;step[0]=0; Min[0]=Max[0]=0; } void add(int c) { c-='a'; int p=last,np=++tot; step[np]=step[p]+1; memset(ch[np],-1,sizeof(ch[np])); Min[np]=Max[np]=step[np]; while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p]; if(p==-1) pre[np]=0; else{ int q=ch[p][c]; if(step[q]!=step[p]+1){ int nq=++tot; step[nq]=step[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); Min[nq]=Max[nq]=step[nq]; pre[nq]=pre[q]; pre[q]=pre[np]=nq; while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p]; } else pre[np]=q; } last=np; } void find(char *s) { int len=strlen(s); int u=0; int tmp=0; REP(i,0,tot) Max[i]=0; REP(i,0,len-1){ int c=s[i]-'a'; if(~ch[u][c]) tmp++,u=ch[u][c]; else{ while(~u&&ch[u][c]==-1) u=pre[u]; if(~u) tmp=step[u]+1,u=ch[u][c]; else tmp=0,u=0; } Max[u]=max(Max[u],tmp); } for(int i=tot;i>=1;i--) Max[pre[i]]=max(Max[pre[i]],Max[i]); REP(i,0,tot) Min[i]=min(Min[i],Max[i]); } int calc() { int res=0; REP(i,0,tot) res=max(res,Min[i]); return res; } };SAM A; char s[maxn]; int len; int main() { freopen("in.txt","r",stdin); scanf("%s",s);len=strlen(s); A.init(); REP(i,0,len-1) A.add(s[i]); while(~scanf("%s",s)) A.find(s); printf("%d ",A.calc()); return 0; }
spoj8222
求一个串的长度为1到len的出现最多的子串的出现的次数。
这道题弄了一下午,终于渐渐清晰了起来,这道题终于使我能够理解pre指针和AC自动机的区别了。pre指针所引出来的parent树真是SAM的精髓。
对于这道题,虽然有很多要写的,但是现在过于兴奋不知从何写起,,,有时间再补上。。。
需要注意的一点是,这里虽然是parent树是拓扑图引出来的,但在parent树递推计算right的时候不能向回文树一样直接逆序遍历,会出错,需要记一下度数做一次拓扑排序。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000100; const int INF=1e9+10; char s[maxn]; int ans[maxn]; struct SAM { int ch[maxn][26]; int pre[maxn],step[maxn]; int last,tot; int right[maxn],in[maxn]; void init() { last=tot=0; memset(ch[0],-1,sizeof(ch[0])); pre[0]=-1; step[0]=0; } void add(int c) { c-='a'; int p=last,np=++tot; step[np]=step[p]+1; memset(ch[np],-1,sizeof(ch[np])); right[np]=1; while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p]; if(p==-1) pre[np]=0; else{ int q=ch[p][c]; if(step[q]!=step[p]+1){ int nq=++tot; memcpy(ch[nq],ch[q],sizeof(ch[q])); step[nq]=step[p]+1; pre[nq]=pre[q]; pre[q]=pre[np]=nq; while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p]; } else pre[np]=q; } last=np; } void calc() { MS0(in); REP(i,1,tot) in[pre[i]]++; queue<int> q; REP(i,1,tot) if(!in[i]) q.push(i); while(!q.empty()){ int u=q.front();q.pop(); if(pre[u]==-1) continue; right[pre[u]]+=right[u]; if(--in[pre[u]]==0) q.push(pre[u]); } MS0(ans); REP(i,1,tot) ans[step[i]]=max(ans[step[i]],right[i]); } };SAM sam; void solve() { sam.init(); int len=strlen(s); REP(i,0,len-1) sam.add(s[i]); sam.calc(); for(int i=len-1;i>=1;i--) ans[i]=max(ans[i],ans[i+1]); REP(i,1,len) printf("%d ",ans[i]); } int main() { freopen("in.txt","r",stdin); while(~scanf("%s",s)){ solve(); } return 0; }
多次询问一个串字典序第k大的子串,先dp递推出个数,然后直接dfs查找就行了。
#include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef long long ll; const int maxn=2000100; const int INF=1e9+10; char s[maxn]; int n;ll k; char ans[maxn];int ansn; struct SAM { int ch[maxn][26]; int pre[maxn],step[maxn]; int last,tot; ll dp[maxn]; void init() { last=tot=0; memset(ch[0],-1,sizeof(ch[0])); pre[0]=-1; step[0]=0; memset(dp,-1,sizeof(dp)); } void add(int c) { c-='a'; int p=last,np=++tot; step[np]=step[p]+1; memset(ch[np],-1,sizeof(ch[np])); while(~p&&ch[p][c]==-1) ch[p][c]=np,p=pre[p]; if(p==-1) pre[np]=0; else{ int q=ch[p][c]; if(step[q]!=step[p]+1){ int nq=++tot; memcpy(ch[nq],ch[q],sizeof(ch[q])); step[nq]=step[p]+1; pre[nq]=pre[q]; pre[q]=pre[np]=nq; while(~p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p]; } else pre[np]=q; } last=np; } ll dfs(int u) { ll &res=dp[u]; if(~res) return res; res=1; REP(c,0,25){ if(~ch[u][c]) res+=dfs(ch[u][c]); } return res; } void find(int u,ll k) { if(u) k--; if(k<=0) return; REP(c,0,25){ int v=ch[u][c]; if(~v){ ll tmp=dfs(v); if(k-tmp<=0){ ans[ansn++]=c+'a'; find(v,k); return; } k-=tmp; } } } };SAM sam; void solve() { sam.init(); int len=strlen(s); REP(i,0,len-1) sam.add(s[i]); REP(i,1,n){ scanf("%d",&k); ansn=0; sam.find(0,k); ans[ansn]='