2017.8.20 后缀自动机(p3804) 思考记录

首先构造sam还是比较巧的

首先它的总点数和边数不会超过2*n,而且每个节点下面相同的字母节点只有一个

所以它实质上是把一个字母用了多次

sam是每次加入一个节点,然后合理的安排位置、边、辅助点使之更优

1、父指针:指向同样能接受下一个字符的节点:

设根节点(起点)为s

如 aca   ,这时第二个a的父指针就指向第一个a,这样下一个节点x就会和这两个a连边,使s-a-c-a-x  和s-a-x 同时存在

如果不用父指针,都从s出发,那s后面就有两个字符为a的节点,显然不优(匹配a时不知道走哪个,就不是自动机了)

所以这第一个a就有两个意义了


父指针使自动机区别于暴力加后缀();


还有添加辅助节点,,如abacc  第二个c会找到第一个c来尝试优化,,但起点后面只能有一个c,并且这个c还肩负着后缀c、cc   那怎么办?

新建一个节点c,复制第一个c和根、子节点的关系,使它具有和第一个c相同的功能,于是,cc串就出现了

它还需要作为后缀c,,所以本身可以接受节点,第一个c和第二个c的父指针都指向它,这样它就相当于有两个意义


这个题要求的使子串长度*出现次数的最大值,,出现次数好说,因为父指针都是指向前面的相同字符的节点,所以倒序递推即可在第一次出现该字符时统计出字符出现次数

还剩一个长度,,l数组就有用了,它记录的是从根节点出发的最长子串、所以计数排序之后,如果它的节点的出现次数为n,那它前面的节点的出现次数也一定是>=n的(因为爬相同的节点需要一个一个爬,不可能跳步),所以size*l取最值即可


码:



#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long 
#define N 1000006  
int cnt,last;
int ch[N<<1][28],fu[N<<1];
ll  sz[N<<1],l[N<<1],ans,c[N<<1],a[N<<1];
char str[N];
void jia(int o)
{
	int np=++cnt,p=last;last=np;l[np]=l[p]+1;
	for(;p&&ch[p][o]==0;p=fu[p])ch[p][o]=np;
	if(!p)fu[np]=1;
	else
	{
		int q=ch[p][o];
		if(l[p]+1==l[q])fu[np]=q;
		else
		{
		int i,nq=++cnt;
		l[nq]=l[p]+1;
		for(i=0;i<=26;i++)
			ch[nq][i]=ch[q][i];
			fu[nq]=fu[q];
			fu[q]=fu[np]=nq;
			for(;ch[p][o]==q;p=fu[p])ch[p][o]=nq;			
		}			
	}
	sz[np]=1;
}
int main()
{
	scanf("%s",str);
	cnt=1;
	last=1;
	int i,n=strlen(str);
	for(i=0;i<n;i++)jia(str[i]-'a');
	for(i=1;i<=cnt;i++)c[l[i]]++;
	for(i=1;i<=cnt;i++)c[i]+=c[i-1];
	for(i=1;i<=cnt;i++)a[c[l[i]]--]=i;
	//for(int i=1;i<=cnt;i++)cout<<a[i]<<" "<<l[a[i]]<<endl;
	for(i=cnt;i>=1;i--)
	{
		int p=a[i];
		sz[fu[p]]+=sz[p];
		if(sz[p]>1)ans=max(ans,sz[p]*l[p]);				
	}
	printf("%lld",ans); 
} 









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值