SPOJ 8222 Substrings 【后缀自动机模板】

题目描述:

给定一个字符串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]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值