spoj 8222 Substrings
f[i]指长度为i的串出现次数的最大值。这里的不同出现指,可以有重复串,只要起始位置不同就视为不同的出现。
求f[1]..f[lenth]。
怎么求可重复的一个串出现了多少次。
LCS那篇里提到了,通过fa指针回退,会回到一个状态,这个状态的接受串与当前状态的接受串后缀是相同的。
那么,设t[i].fa=j,那么i状态的接受串也必定出现在j状态的接受串的后缀中。又注意到ij的接受串的结束位置是不同的,这样,i状态至少出现了两次。
通过这样的规律,通过fa边的拓扑序dp统计答案即可。
还有一点,一个状态的接受串有可能有多种。如aab,b有可能被同一个状态接受。
如果aab出现了n次,那么b也可能出现n次。而aab的任意子串如aa、ab也出现n次。
这样只需要从最长到最短更新dp的值,就可以不漏掉任何情况。
WA了很久,一直卡在一个地方。翻了很多题解才有所进展。
做初始化时,dp[i]不能全部置为1,而是当i不是分裂的节点(就是构造函数中的nq)这样的节点才置1。
很容易想到,一个节点分裂了,如果都置1,父节点会重复计算。
但是该分裂节点如何保证正确计数?因为构造中,新分裂的节点nq会成为q的父亲,因此q的计数也会加到nq上,就保证了计数的正确。
huyuncong这位大牛写的后缀自动机性质很棒。网址如下:
http://blog.csdn.net/huyuncong/article/details/7583214
引用并补充大牛博客的总结:
一个串的子串有多少之类的问题,或是询问子串/后缀的问题,就用子边转移(自动机性质)。
而计算一个串重复出现次数(right集合的问题),回退到最长匹配状态(LCS问题),就用父边转移(后缀树性质)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define Maxn 250009
int root,last;//sam
int tots;
struct sam_node{
int fa,son[26];
int len;
void init(int _len){len=_len;fa=-1;memset(son,-1,sizeof(son));}
}t[Maxn*2];//length*2
void sam_init(){
tots=0;
root=last=0;
t[tots].init(0);
}
void extend(int w){
int p=last;
int np=++tots;t[tots].init(t[p].len+1);
int q,nq;
while(p!=-1&&t[p].son[w]==-1){t[p].son[w]=np;p=t[p].fa;}
if (p==-1) t[np].fa=root;
else{
q=t[p].son[w];
if (t[p].len+1==t[q].len){t[np].fa=q;}
else{
nq=++tots;t[nq].init(0);
t[nq]=t[q];
t[nq].len=t[p].len+1;
t[q].fa=nq;t[np].fa=nq;
while(p!=-1&&t[p].son[w]==q){t[p].son[w]=nq;p=t[p].fa;}
}
}
last=np;
}
int w[Maxn],r[Maxn*2],l;
void topsort(){
int i;
for(i=0;i<=l;++i) w[i]=0;
for(i=1;i<=tots;++i)w[t[i].len]++;
for(i=1;i<=l;++i) w[i]+=w[i-1];
for(i=tots;i>=1;--i)r[w[t[i].len]--]=i;
r[0]=0;
}
int dp[Maxn*2],f[Maxn];
char s[Maxn];
int main(){
int i,p;
scanf("%s",s);
l=strlen(s);
sam_init();
for(i=0;i<l;++i){
extend(s[i]-'a');
}
topsort();
//for(i=0;i<=tots;++i) {dp[i]=1;} //wrong initializing
for(i=0;i<=tots;++i) {dp[i]=0;}
p=root;
for(i=0;i<l;++i){
p=t[p].son[s[i]-'a'];
dp[p]++;
}
for(i=tots;i>=1;--i){
p=r[i];
if (t[p].fa!=-1) dp[t[p].fa]+=dp[p];
}
for(i=1;i<=l;++i) f[i]=0;
for(i=1;i<=tots;++i)
if (dp[i]>f[t[i].len]) f[t[i].len]=dp[i];
for(i=l-1;i>=1;--i){
f[i]=f[i]<f[i+1]?f[i+1]:f[i];
}
for(i=1;i<=l;++i){
printf("%d\n",f[i]);
}
return 0;
}