●SPOJ 8222 NSUBSTR–Substrings(后缀自动机)
题链:
http://www.spoj.com/problems/NSUBSTR/
题解:
后缀自动机的水好深啊!
懂不了相关证明,带着结论把这个题做了。
看来这滩深水要以后再来了。
本题要用到一个叫 Right[P] 的数组,
表示 P对应的子串在原串中出现的所有位置的末尾位置下标的集合。
本题中,用这个数组存储集合大小就好了,即 P对应的子串在原串中出现了Right[p]次。
而Right[P]的值,等于从改点出发到结束状态的方案数。
但这个不好求,而是要用到另一个求法:用 Parent树:
(暂时由结论可知)Right集合不相交,只存在并列和包含关系。
所以把原来的 pre[]的指向反向后,形成一棵树,那么父节点的Right值=sigma(子节点节点的Right)
又因为这个Parent树无法遍历,且注意到儿子节点的step一定大于父亲节点的step,
所以对所有节点桶排序后,反向枚举,去更新父亲就好了。
计算出了Right[P]后,答案就可以统计了,详见代码。
代码:
#include<cstdio> #include<cstring> #include<iostream> #define MAXN 500100 #define filein(x) freopen(#x".in","r",stdin); #define fileout(x) freopen(#x".out","w",stdout); using namespace std; struct SAM{ int size,last,q,p,nq,np,len; int pre[MAXN],step[MAXN],Right[MAXN],ch[MAXN][26]; int Newnode(int a,int b){ step[size]=a; memcpy(ch[size],ch[b],sizeof(ch[b])); return size++; } void Extend(int x){ p=last; last=np=Newnode(step[p]+1,0); while(p&&!ch[p][x]) ch[p][x]=np,p=pre[p]; if(!p) pre[np]=1; else{ q=ch[p][x]; if(step[q]!=step[p]+1){ nq=Newnode(step[p]+1,q); pre[nq]=pre[q]; pre[q]=pre[np]=nq; while(p&&ch[p][x]==q) ch[p][x]=nq,p=pre[p]; } else pre[np]=q; } } void Make_Right(char *S){ static int c[MAXN],T[MAXN]; p=1; for(int i=0;i<len;i++) p=ch[p][S[i]-'a'],Right[p]++; memset(c,0,sizeof(c)); for(int i=1;i<size;i++) c[step[i]]++; for(int i=1;i<=len;i++) c[i]+=c[i-1]; for(int i=1;i<size;i++) T[c[step[i]]--]=i; for(int i=size-1;i;i--) Right[pre[T[i]]]+=Right[T[i]]; } void Build(char *S){ len=strlen(S); memset(ch[0],0,sizeof(ch[0])); size=1; last=Newnode(0,0); pre[last]=0; for(int i=0;i<len;i++) Extend(S[i]-'a'); Make_Right(S); } }suf; int ANS[MAXN]; char S[MAXN]; int main() { scanf("%s",S); int len=strlen(S); suf.Build(S); for(int i=1;i<suf.size;i++) ANS[suf.step[i]]=max(ANS[suf.step[i]],suf.Right[i]); for(int i=len-1;i;i--) ANS[i]=max(ANS[i],ANS[i+1]); for(int i=1;i<=len;i++) printf("%d\n",ANS[i]); return 0; }
Do not go gentle into that good night.
Rage, rage against the dying of the light.
————Dylan Thomas