若是不算pre指針的話後綴自動機就是一個DAG,這是它能很方便地進行dp的前提。node
而pre指針返回什麼呢,返回的就是上一個的前綴包含改結點所表明子串的那個後綴,和AC自動機上的fail指針很像,都是爲了匹配。我目前學得不深,看不出和AC自動機的fail指針有什麼區別,用起來也幾乎同樣。c++
相比於字典樹和迴文樹,後綴自動機每一個結點會有多個父結點,能夠表示多種子串(從根節點到它的每條路徑都是一個子串),所以子串的信息只能在路徑中記錄,好比長度,而該子串說記錄的長度step,則是根結點到它的最遠距離,而某個子串的長度就是該表明該子串的路徑的長度了,並不必定是某個結點的step。數組
spoj1811ide
求兩個串的最長公共子串的長度。ui
對A串創建後綴自動機,對B串進行匹配,若是匹配失敗,沿着失敗指針往回走到第一個能匹配的位置繼續匹配(看起來似曾相識?沒錯,這不是AC自動機的過程嗎。。。),固然若是到根節點還不能繼續匹配,那就只有從頭再來了。這裏隨時記錄長度更新答案便可。spa
固然後綴數組也能夠作,把兩個串拼接起來,求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\n",sam.find(t)); } int main() { freopen("in.txt","r",stdin); while(~scanf("%s%s",s,t)){ solve(); } return 0; }
spoj1812code
求多個串的最長公共子串的長度。blog
對第一個串創建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\n",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\n",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]='\0'; puts(ans); } } int main() { freopen("in.txt","r",stdin); scanf("%s",s); scanf("%d",&n); solve(); return 0; }
-------------------------
關於LCT維護SAM,目的就是在線維護right數組,就是用LCT去在線維護parent樹,其實就是在線維護right集合的時候須要在鏈接結點u和pre[u]維護parent樹時須要在u到根的路徑上的結點的right值所有+1,而後鏈接結點涉及刪邊加邊,因此用LCT。。。例題,bzoj2555。
bzoj2555:
如上,用LCT去維護parent來實如今線維護SAM,調了一下午。。。
#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=3200100; const int INF=1e9+10; char s[maxn]; int len; char op[20]; int Q; struct LCT { int pre[maxn],ch[maxn][2],rev[maxn]; int val[maxn],add[maxn]; void init() { MS0(pre);MS0(ch);MS0(rev);MS0(add); MS0(val); } void update_add(int x,int w) { if(!x) return; val[x]+=w; add[x]+=w; } void update_rev(int x) { if(!x) return; swap(ch[x][0],ch[x][1]); rev[x]^=1; } void down(int x) { if(add[x]){ update_add(ch[x][0],add[x]); update_add(ch[x][1],add[x]); add[x]=0; } if(rev[x]){ update_rev(ch[x][0]); update_rev(ch[x][1]); rev[x]=0; } } bool isroot(int x) { return ch[pre[x]][0]!=x&&ch[pre[x]][1]!=x; } void up(int x) { } void P(int x) { if(!isroot(x)) P(pre[x]); down(x); } void rot(int x,int kind) { int y=pre[x]; ch[y][kind^1]=ch[x][kind]; pre[ch[x][kind]]=y; if(!isroot(y)) ch[pre[y]][ch[pre[y]][1]==y]=x; pre[x]=pre[y]; ch[x][kind]=y; pre[y]=x; up(y); } void splay(int x) { P(x); while(!isroot(x)){ if(isroot(pre[x])) rot(x,ch[pre[x]][0]==x); else{ int y=pre[x],z=pre[y]; int kind=ch[y][0]==x,one=0; if(ch[y][0]==x&&ch[z][0]==y) one=1; if(ch[y][1]==x&&ch[z][1]==y) one=1; if(one) rot(y,kind),rot(x,kind); else rot(x,kind),rot(x,kind^1); } } up(x); } int access(int x) { int t=0; while(x){ splay(x); ch[x][1]=t;t=x;x=pre[x]; up(t); } return t; } void makeroot(int x) { access(x);splay(x);update_rev(x); } void ADD(int x,int y,int w) { makeroot(x);access(y);splay(y); update_add(y,w); } void cut(int x,int y) { makeroot(x);access(y);splay(y);ch[y][0]=pre[x]=0;up(y); ADD(y,1,-val[x]); } void link(int x,int y) { makeroot(x);pre[x]=y;up(y); ADD(y,1,val[x]); } int query(int x) { splay(x); return val[x]; } };LCT lct; struct SAM { int ch[maxn][26]; int pre[maxn],step[maxn]; int last,tot; int newnode(int k) { ++tot; step[tot]=k; MS0(ch[tot]); pre[tot]=0; return tot; } void init() { tot=0; last=newnode(0); lct.init(); } void add(int c) { c-='A'; int p=last,np=newnode(step[p]+1); lct.val[np]=1; while(p&&ch[p][c]==0) ch[p][c]=np,p=pre[p]; if(p==0) pre[np]=1,lct.link(np,1); else{ int q=ch[p][c]; if(step[q]!=step[p]+1){ int nq=newnode(step[p]+1); memcpy(ch[nq],ch[q],sizeof(ch[q])); lct.cut(q,pre[q]); lct.link(nq,pre[q]); lct.link(q,nq); lct.link(np,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,lct.link(np,q); } last=np; } int find(char *s) { int len=strlen(s); int u=1; REP(i,0,len-1){ int c=s[i]-'A'; if(ch[u][c]) u=ch[u][c]; else return 0; } return lct.query(u); } };SAM sam; void decode(char *s,int mask) { int len=strlen(s); REP(i,0,len-1){ mask=(mask*131+i)%len; swap(s[i],s[mask]); } } int main() { freopen("in.txt","r",stdin); while(~scanf("%d",&Q)){ scanf("%s",s); len=strlen(s); sam.init(); REP(i,0,len-1) sam.add(s[i]); int mask=0; REP(i,1,Q){ scanf("%s%s",op,s); decode(s,mask); if(op[0]=='A'){ len=strlen(s); REP(i,0,len-1) sam.add(s[i]); } else{ int res=sam.find(s); printf("%d\n",res); mask^=res; } } } return 0; }
---------------------------