题目描述:
给定一个字符串S,ans[len]为长度为len的串在S中出现次数的最大值。
求ans[1]…ans[|S|]
题目分析:
后缀自动机模板,通过后缀链接累计出现次数cnt[v],等于其终点集合的大小。
对每个状态,如果它不是由“拷贝”而来,最初就赋值cnt=1,否则cnt=0,然后我们按长度len降序遍历所有序列,并将当前的cnt[v]加给后缀链接:cnt[link(v)]+=cnt[v].
ans[len]就是路径长度为len的cnt最大值,用自动机中的节点更新即可
因为长度为len+1的串一定包含了长度为len的串,所以最后可以ans[len]=max(ans[len],ans[len+1])
但是实际上是没有必要的,这种ans[len+1]>ans[len]的情况并不存在,就算可能,无非是这种情况:
但是bc的出现次数是一定可以统计到的,因为后缀自动机一定可以接受串bc,这说明从起点出发一定会有一个bc串,假设走到了点p,如果len[p]=2,那么bc的次数就被统计到了,如果len[p]>2,那么肯定还有一个跟bc同终点集合的串,那个串的前两位对应的串的出现次数大于等于bc的出现次数,如果那个串对应的len[p’]也大于2,又可以一直找下去,直到len[p’’]=2,答案就会被统计到。
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 250005
using namespace std;
struct node{
int len,fail,nxt[26],cnt;
}st[maxn<<1];
int siz,last,n,ans[maxn],id[maxn<<1],b[maxn];
char a[maxn];
void sa_init(){
siz=last=0;
st[0].fail=-1;
}
void sa_extend(int c)
{
int cur=++siz,p=last;
st[cur].len=st[last].len+1;
st[cur].cnt=1;
for(;p!=-1&&!st[p].nxt[c];p=st[p].fail) st[p].nxt[c]=cur;
if(p==-1) st[cur].fail=0;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len) st[cur].fail=q;
else{
int clone=++siz;
st[clone]=st[q],st[clone].len=st[p].len+1,st[clone].cnt=0;
st[q].fail=st[cur].fail=clone;
for(;p!=-1&&st[p].nxt[c]==q;p=st[p].fail) st[p].nxt[c]=clone;
}
}
last=cur;
}
inline bool cmp(int x,int y){return st[x].len<st[y].len;}
int main()
{
scanf("%s",a);
n=strlen(a);
sa_init();
for(int i=0;i<n;i++) sa_extend(a[i]-'a');
for(int i=1;i<=siz;i++) b[st[i].len]++;
for(int i=1;i<=n;i++) b[i]+=b[i-1];
for(int i=1;i<=siz;i++) id[b[st[i].len]--]=i;//桶排序,类似后缀数组的sa
for(int i=siz;i>=1;i--) st[st[id[i]].fail].cnt+=st[id[i]].cnt;
for(int i=1;i<=siz;i++) ans[st[i].len]=max(ans[st[i].len],st[i].cnt);
for(int i=n-1;i>=1;i--) ans[i]=max(ans[i],ans[i+1]);//这里不要dfs
//上面这条语句删掉也可以
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
}