后缀自动机总结
第一次写一个算法的总结
////////////////////////////////////////////////////////////////////////////////////
poj 1509 Glass Beads
题目要求一个字符串的最小表示,在SAM上面走,可以找到这个字符串的所以子串,这样我们可以把 string str 重复一次建SAM
然后在SAM上面每次往最小的走 len 步,这样得到的就是题目要求的
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 20010 #define pi acos(-1.0) #define eps 1e-6 using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } char str[maxn]; void init(int n ) { tot=0; for(int i = 0 ; i < n ;i++) { que[i].g = 0 ; que[i].pre=NULL; memset(que[i].son,0,sizeof(que[i].son)) ; } root=tail=&que[tot++] ; } int solve(int n ) { for( int i = 1 ; i <= n ;i++) { add(str[i-1]-'a',i) ; } for( int i = 1 ; i <= n ;i++) { add(str[i-1]-'a',i+n) ; } SAM *now = &que[0] ; for(int i = 0 ; i < n ;i++) { for(int j = 0 ;j < 26 ;j++) { if(now->son[j] != NULL) { now = now->son[j] ; break ; } } } return now->len ; } int main() { int m,n,i,j,k; int u,v,c; while(scanf("%d",&n) != EOF) { while(n--) { scanf("%s",str) ; m = strlen(str) ; init(m*2); printf("%d\n",solve(m)-m+1) ; } } return 0 ; }
hdu 4270 Dynamic Lover
题意 :1.插入一个字符串,2 .询问1-len开头的,长度为len的(如果有的话就是len,没有就是后缀了),最小子串是谁,3,删除末尾 长为 len 的子串
对于询问,和上面一样,走len步,这里有可能是长度不为len,所以把后缀标记一下就好了。对于 1,直接插入,对于 3,
我们在后缀自动机增加标记 *del,这个指向一个数组。而且它复制出来的节点和它指向地址是一样的,这样删除就只要标记一个就好了。
还有在插入的时候,用pos[i]记录长度为i的字符在SAM位置。删的时候,就从n-len+1删除到n就好了,注意删除后的末尾节点也要改变
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 300010 #define INF 0x3f3f3f3f using namespace std; struct SAM { SAM *pre,*son[26] ; int e,id; bool *del; int len ,g; void init() { pre=NULL ; e=0; memset(son,NULL,sizeof(son)) ; } }que[maxn],*root,*tail,*b[maxn]; int tot ,tt ; int kk ,n ,cnt,pos[maxn]; bool num[maxn]; void add(int c ,int l) { que[tot].init(); SAM *p = tail,*np=&que[tot++] ; np->del = &num[tt++]; np->len=np->id=l;tail=np ; pos[l]=tot-1; while(p&&(p->son[c]==NULL||(*p->son[c]->del)))p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } int dfs(SAM *p ,int len) { if(len==kk) return p->id-kk+1 ; if(p->e==cnt) return n-len+1 ; for(int i = 0 ; i < 26 ;i++)if(p->son[i]&&!(*p->son[i]->del)) return dfs(p->son[i],len+1) ; } void Delete(int len ) { for(int i = n-len+1 ; i <= n ;i++)*(que[pos[i]].del)=true; n -= len ; tail = &que[pos[n]]; } void init() { tot=0; tt=0; cnt=0; memset(num,0,sizeof(num)) ; que[tot].init(); root=tail=&que[tot++] ; root->del = &num[tt++] ; } char a[maxn] ; int main() { int i ,m,k ,j ; int T ,case1=0,len; while(scanf("%s",a) !=EOF) { n = strlen(a) ; init(); for( i = 1 ; i <= n ;i++) add(a[i-1]-'a',i); scanf("%d",&m) ; while(m--) { scanf("%d",&k) ; if(k==1) { scanf("%s",a) ; len=strlen(a) ; for( i = 0 ; i < len ;i++) add(a[i]-'a',++n) ; } else if(k==2) { SAM *p ; cnt++; p = tail ; while(p != NULL &&p != root){ p->e = cnt ; p = p->pre ; } root->e=0; p = root ; scanf("%d",&kk) ; printf("%d\n",dfs(p,0)) ; } else { scanf("%d",&j) ; Delete(j); } } } return 0 ; }
hdu 4416 Good Article Good sentence
题意:给出 string a ,和 string b[] ,询问a一共有多少个不同的子串 不是b[i]的子串
对a建立 SAM,然后经行拓扑排序。然后b[i]去SAM上面匹配,对于节点 p 我们记录所有b[i]中长度最长的匹配,p->g
因为父辈的子串由儿子那里得到,我们从后面开始计算答案,然后把p->g传给父亲。
计算的时候,对于节点 p ,以它为结尾的字串由p->len 个,它和父亲重叠的部分是 p->pre->len ,
和b[i]字串重叠最长p->g ,所以得到合法子串 p->len - max(p->pre->len ,p->g ) ;
注意SAM得到的字串都是不重复的,所以计算出来的就是所以不重复的字串。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 200010 #define INF 0x3f3f3f3f using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } void init(int n ) { tot=0; for( int i = 0 ; i <= n ;i++) { que[i].pre = NULL ; que[i].g=0; memset(que[i].son,0,sizeof(que[i].son)) ; } root=tail=&que[tot++] ; } char a[maxn] ; int C[maxn] ; LL solve(int n ,int m) { memset(C,0,sizeof(C)) ; for(int i = 0 ; i < tot;i++) C[que[i].len]++ ; for(int i = 1 ; i <= n ;i++)C[i] += C[i-1] ; for(int i = 0 ; i < tot ;i++)b[--C[que[i].len]] =&que[i] ; SAM *p ; int tmp,len,i; while(m--) { p = &que[0] ; tmp=0; scanf("%s",a) ; len = strlen(a) ; for( i = 0 ; i < len ;i++) { if(p->son[a[i]-'a']) { tmp++ ; p = p->son[a[i]-'a']; } else { while(p && p->son[a[i]-'a']==NULL) p = p->pre ; if(p==NULL) { tmp=0; p = &que[0] ; } else { tmp = p->len+1; p = p->son[a[i]-'a']; } } p->g = max(p->g,tmp) ; } } LL ans = 0 ; for( i = tot-1 ; i > 0 ;i--) { p = b[i]; if(p->pre){ tmp = max(p->pre->len,p->g); p->pre->g = max(p->pre->g,p->g ) ; } else tmp = p->g ; if(p->len<tmp)tmp=p->len; ans += p->len-tmp ; } return ans; } int main() { int i , n, m,k ,j ; int T ,case1=0; cin >> T ; while( T--) { scanf("%d",&m) ; scanf("%s",a); n = strlen(a) ; init(n*2); for( i = 1 ; i <= n ;i++) add(a[i-1]-'a',i); printf("Case %d: ",++case1); cout << solve(n,m) << endl; } return 0 ; }
hdu 4622 Reincarnation
题意:给出 string a ,给出l,r区间,询问这个居间不同子串个数
离线,记录 l,下所有的询问 r 。对于不同l ,每次我们都重构SAM
查找的时候,对于节点 p , 以它结尾的有p->len 个子串,和父亲重叠的 p->pre->len
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 10010 #define INF 0x3f3f3f3f using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ,num; bool vi; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } char str1[10010]; void init(int n ) { tot=0; for(int i = 0 ; i <= n ;i++) { memset(que[i].son,0,sizeof(que[i].son)) ; que[i].num = 0 ; que[i].pre=NULL; } root=tail=&que[tot++] ; } int find() { int ans=0; for(int i =1 ; i < tot;i++) { SAM *p = que[i].pre ; if(p != NULL ) ans += que[i].len-p->len ; else ans += que[i].len ; } return ans; } struct node { int R,id ; bool operator<(const node&s) const { return R < s.R ; } }; vector<node>qe[maxn] ; int ans[maxn]; int main() { int m,n,i,j; int u,v,c,tmp; int T ,len; node a; cin >> T ; while(T--) { scanf("%s",str1+1) ; scanf("%d",&m) ; n = strlen(str1+1) ; for( i =1 ; i <= n ;i++) qe[i].clear(); for( i = 1 ; i <= m ;i++){ scanf("%d%d",&u,&v); a.R =v ; a.id= i ; qe[u].push_back(a) ; } for( i =1 ; i <= n ;i++) sort(qe[i].begin(),qe[i].end()); for( i = 1 ; i <= n ;i++) { len = qe[i].size(); if(len==0) continue ; u = 0 ; v=1; init(n*2); for( j = i ; j <= n ;j++) { add(str1[j]-'a',v++); while(u < len && qe[i][u].R == j) { ans[qe[i][u].id] = find(); u++; } if(u==len) break ; } } for( i = 1 ; i <= m ;i++) printf("%d\n",ans[i]); } return 0 ; }
hdu 4641 K-string
题意:1, 插入一个字符,2,询问出现 k次以上的不同子串个数
记住,父辈节点拓展节点由儿子得到,这样我们可以,
对于每个状态,多开一个值记录它的出现次数,每次添加点过后,沿着tail 点的pre指正一边走一边判断就好了。
注意,每个点不要重复计算,如果一个点计算前就已经被标记计算了,那么pre上的其他的点也不需要计
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 500010 #define INF 0x3f3f3f3f using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ,num; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } int k ; LL ans; void count() { SAM *now = tail ; for( ;now!=NULL;now=now->pre) { if(now->num==k)return ; now->num++; if(now->num==k) { if(now->pre) ans += now->len-now->pre->len ; else ans += now->len ; return ; } } } char str1[500010]; void init(int n ) { tot=0; for(int i = 0 ; i <= n ;i++) { memset(que[i].son,0,sizeof(que[i].son)) ; que[i].num = 0 ; que[i].pre=NULL; } root=tail=&que[tot++] ; } int main() { int m,n,i,j; int u,v,c,tmp; char a[2]; while(scanf("%d%d%d",&n,&m,&k) != EOF) { scanf("%s",str1) ; init(n*2+m*2) ; ans=0; for( i = 1 ; i <= n ;i++){ add(str1[i-1]-'a',i) ; count(); } while(m--) { scanf("%d",&tmp) ; if(tmp==2) printf("%I64d\n",ans) ; else { scanf("%s",a) ; add(a[0]-'a',i++); count(); } } } return 0 ; }
spoj 1811 Longest Common Substring
题意:求最长公共子串
对于一个串,建好 SAM,然后 拿另一个上去匹配,记录匹配最长距离就好了
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 500010 #define pi acos(-1.0) #define eps 1e-6 using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ; }que[maxn],*root,*tail; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } char str1[maxn],str2[maxn]; void init(int n ) { tot=0; for(int i = 0 ; i < n ;i++) { que[i].g = 0 ; que[i].pre=NULL; memset(que[i].son,0,sizeof(que[i].son)) ; } root=tail=&que[tot++] ; } int main() { int m,n,i,j,k; int u,v,c,tmp,ans; while(scanf("%s%s",str1,str2) != EOF) { n = strlen(str1) ; init(n); for( i = 1 ; i <= n ;i++) add(str1[i-1]-'a',i) ; m = strlen(str2) ; ans=0; tmp=0; SAM *now=&que[0]; for( i = 0 ; i < m ;i++) { c = str2[i]-'a'; if(now->son[c] != NULL) { tmp++; now = now->son[c] ; } else { for( ; now!=NULL&&now->son[c]==NULL;now=now->pre); if(now != NULL) { tmp = now->len+1 ; now=now->son[c]; } else now=&que[0],tmp=0; } ans=max(ans,tmp); } cout << ans << endl; } return 0 ; }
CF 235 C
题意:给出 string a ,每次 求一个串X的所以循环串在 a 里面出现的次数
对a建好SAM后,经行拓扑排序,然后对预处理每个节点表示状态出现的次数,
然后把b复制一次拿上SAM匹配,如果到 P 匹配长度 >= strlen(b) ,那么说明
匹配成功,然后往 p的父亲节点找 最深的匹配点(这样才能完整计数),
也就是符合 len >=pp->pre->len+1 && len <= pp->len 的点,
然后加入答案,然后标记走过。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 2000010 #define INF 0x3f3f3f3f using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ,vi; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } void init(int n) { tot=0; for(int i = 0 ; i <= n ;i++) { que[i].vi=-1; que[i].g = 0 ; que[i].pre = NULL ; memset(que[i].son,NULL,sizeof(que[i].son)) ; } root=tail=&que[tot++]; } char a[maxn/2]; int C[maxn] ; int main() { int i ,m ,n , j ,k ,tmp ; int T ,sz ,len,ans ; while(scanf("%s",a) != EOF) { n = strlen(a) ; init(n*2); for(i = 0 ; i < n ;i++) add(a[i]-'a',i+1) ; memset(C,0,sizeof(C)) ; for(i = 0 ; i < tot ;i++)C[que[i].len]++ ; for(i = 1 ; i <= n ;i++)C[i] += C[i-1] ; for(i = 0 ; i < tot ;i++)b[--C[que[i].len]] = &que[i] ; SAM *p = root->son[a[0]-'a'] ; i = 0 ; for(; p != NULL ;p = p->son[a[i]-'a']) { p->g = 1 ; i++; if(i==n) break ; } for(i = tot-1 ; i >= 1 ;i--) { p=b[i] ; if(p->pre)p->pre->g += p->g ; } scanf("%d",&m) ; sz=1; while(m--) { scanf("%s",a) ; len = strlen(a) ; p = root ; tmp=0; ans=0;sz++; for(j = 0 ; j < len+len ;j++) { if(j>=len) i = j-len ; else i = j ; if(p->son[a[i]-'a'] != NULL ) { tmp++ ; p = p->son[a[i]-'a'] ; } else { while(p != NULL &&p->son[a[i]-'a']==NULL) p = p->pre ; if(p==NULL) { p = root ; tmp=0; } else { tmp = p->len+1 ; p=p->son[a[i]-'a'] ; } } if(tmp>=len) { SAM *pp = p ; while(pp){ if(len>=pp->pre->len+1 && len <= pp->len){ break ; } pp = pp->pre ; } if(pp->vi != sz) { pp->vi=sz ; ans += pp->g ; // cout << pp->g << endl; } } } printf("%d\n",ans) ; } } return 0 ; }
spoj 8222. Substrings
题意 :求长度为 len的子串出现最多是多少次,len 取 1~strlen(a)
经过了拓扑排序,我们可以处理出每个状态出现的次数。
处理的时候,就可以把当前状态的子串搞出来
最后用短的更新长的就好了
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<set> #include<stack> #include<map> #include<ctime> #include<bitset> #define LL long long #define mod 1000000007 #define maxn 500010 #define pi acos(-1.0) #define eps 1e-6 using namespace std; struct SAM { SAM *pre,*son[26] ; int len ,g ; }que[maxn],*root,*tail,*b[maxn]; int tot ; void add(int c ,int l) { SAM *p = tail,*np=&que[tot++] ; np->len=l;tail=np ; while(p&&p->son[c]==NULL)p->son[c]=np,p=p->pre ; if(p==NULL) np->pre = root ; else { SAM *q = p->son[c] ; if(p->len+1==q->len)np->pre = q ; else { SAM *nq = &que[tot++] ; *nq=*q ; nq->len = p->len+1; np->pre=q->pre=nq; while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre; } } } char str1[maxn]; void init(int n ) { tot=0; for(int i = 0 ; i < n ;i++) { que[i].g = 0 ; que[i].pre=NULL; memset(que[i].son,0,sizeof(que[i].son)) ; } root=tail=&que[tot++] ; } int dp[maxn],C[maxn] ; int main() { int m,n,i,j,k; int u,v,c,tmp,ans; while(scanf("%s",str1) != EOF) { n = strlen(str1) ; init(n); for( i = 1 ; i <= n ;i++) add(str1[i-1]-'a',i) ; SAM *now=&que[0] ; memset(dp,0,sizeof(dp)) ; for( i = 0 ; i < n ;i++) { now = now->son[str1[i]-'a']; now->g++; } for( i = 0 ; i < tot ;i++)C[que[i].len]++; for( i = 1 ; i <= n ;i++) C[i] += C[i-1] ; for( i = 0 ; i < tot ;i++) b[--C[que[i].len]]=&que[i] ; for( i = tot-1 ; i >=0 ;i--) { dp[b[i]->len]= max(dp[b[i]->len],b[i]->g) ; if(b[i]->pre) { b[i]->pre->g += b[i]->g ; } } for( i = n-1 ; i >= 1 ;i--) dp[i]=max(dp[i+1],dp[i]) ; for( i = 1 ; i <= n ;i++) printf("%d\n",dp[i]); } return 0 ; }