Cool Slogans,CF700E,SAM推结论题

本文探讨了基于Sufffix Automaton(SA)优化的最长串匹配问题,提出两个关键引理,通过fail树遍历与子节点匹配检查,确保串在子节点管理串中的等频出现,实现高效字符串匹配。

正题

      Portal

      引理1:当k最大时,存在一组解满足s[i+1]是s[i]的后缀

      如果存在最大的k为答案,若有s[i+1]不是s[i]的后缀,那么将s[i]的一部分后缀去掉,使得s[i+1]是s[i]的后缀,显然对k值没有影响.

      有了这个引理,我们就可以在fail树上暴力往下跳了,只要对与父亲的每一个len,看看在子节点的right集合中[l+len-1,r-1]中是否有点即可.

      引理2:父亲节点所管理的每一个串,在子节点所管理串中的出现次数相等.

      让我们来举个例子证明:

      若a="bcd",b="abcd",a,b属于一个等价类x

      若s="bcdeabcd",s属于等价类p,且s为p中的最长串,x为p的父亲.

      此时a在s中出现了一次,b在s中出现了两次.由于a,b的right集合相等,那么必然存在一个串w="abcdeabcd",由于s是p中最长串,所以w所属的等价类y必然是p的孩子,那么|right(p)|>|right(y)|,存在h属于right(p),而不属于right(y),那么也就是说有一个位置有s串没有w串,同时说明了一个地方有a串而没有b串,这与a,b属于同一个等价类x矛盾.

      我们找祖先的最长就好.

#include<bits/stdc++.h>
using namespace std;

const int N=400010;
int rt[N],ls[25000010],rs[25000010],T,f[N],g[N];

void insert(int&now,int x,int l,int r){
	if(now==0) now=++T;
	if(l==r) return ;
	int mid=(l+r)/2;
	if(x<=mid) insert(ls[now],x,l,mid);
	else insert(rs[now],x,mid+1,r);
}

int merge(int x,int y){
	if(!x || !y) return x|y;
	int now=++T;
	ls[now]=merge(ls[x],ls[y]);
	rs[now]=merge(rs[x],rs[y]);
	return now;
}

bool gs(int now,int x,int y,int l,int r){
	if(now==0) return false;
	if(x==l && y==r) return true;
	int mid=(l+r)/2;
	if(y<=mid) return gs(ls[now],x,y,l,mid);
	else if(mid<x) return gs(rs[now],x,y,mid+1,r);
	else return gs(ls[now],x,mid,l,mid)|gs(rs[now],mid+1,y,mid+1,r);
}

struct Saffix_AutoMaton{
	int n,tot,rig[N],fail[N],len[N],ch[N][26],las,sum[N],a[N];
	char s[N];
	void init(int x){memset(ch[x],0,sizeof(ch[x]));fail[x]=rig[x]=len[x]=0;}
	void pre(){init(0);tot=las=0;fail[0]=-1;}
	void extend(int pos,int c){
		int x=++tot,p=las;
		len[x]=len[p]+1;las=x;rig[x]=pos;insert(rt[x],pos,1,n);
		while(p!=-1 && !ch[p][c]) ch[p][c]=x,p=fail[p];
		if(p==-1) fail[x]=0;
		else if(len[ch[p][c]]==len[p]+1) fail[x]=ch[p][c];
		else{
			int q=++tot,tmp=ch[p][c];init(q);
			len[q]=len[p]+1;fail[q]=fail[tmp];fail[x]=fail[tmp]=q;
			for(int i=0;i<26;i++) ch[q][i]=ch[tmp][i];
			while(p!=-1 && ch[p][c]==tmp) ch[p][c]=q,p=fail[p];
		}
	}
	void build(){
		scanf("%d",&n);
		scanf("%s",s+1);pre();
		//n=strlen(s+1);
		for(int i=1;i<=n;i++) extend(i,s[i]-'a'),sum[i]=0;
		for(int i=1;i<=tot;i++) sum[len[i]]++;
		for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
		for(int i=1;i<=tot;i++) a[sum[len[i]]--]=i;
		for(int i=tot;i>=1;i--){
			rig[fail[a[i]]]=max(rig[fail[a[i]]],rig[a[i]]);
			rt[fail[a[i]]]=merge(rt[fail[a[i]]],rt[a[i]]);
		}
	}
	void solve(){
		int ans=1;
		for(int i=1;i<=tot;i++){
			if(fail[a[i]]==1){f[a[i]]=1,g[a[i]]=a[i];}
			if(gs(rt[g[fail[a[i]]]],rig[a[i]]-len[a[i]]+len[g[fail[a[i]]]],rig[a[i]]-1,1,n))
				f[a[i]]=f[fail[a[i]]]+1,g[a[i]]=a[i];
			else f[a[i]]=f[fail[a[i]]],g[a[i]]=g[fail[a[i]]];
			ans=max(ans,f[a[i]]);
		}
		printf("%d\n",ans);
	}
}SAM;

int main(){
	SAM.build();SAM.solve();
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值