codeforces1063F String Journey SAM+DP+dfs序+线段树

题目分析

实际上,分出来的所有 t t t的长度应该是连续的,因为如果不连续的话,删掉过长的 t t t首尾几个字符即可。于是可知, t i + 1 t_{i+1} ti+1应该是 t i t_i ti删掉首字母或者尾字母形成的字符串。

将原串反过来,前缀就变成了后缀。

d p ( i ) dp(i) dp(i)表示以字符 i i i结尾划分为最后一个字符串,可以划分的最大长度。

由于以 i i i结尾的字符串和以 i − 1 i-1 i1结尾的字符串,会有一些公共部分,所以 d p ( i − 1 ) ≥ d p ( i ) − 1 dp(i-1) \geq dp(i)-1 dp(i1)dp(i)1。于是就可以线性地枚举 d p ( i ) dp(i) dp(i)的值,假设是 x x x

这个 x x x合法的条件是,存在小于等于位置 i − x i-x ix的位置,和前缀 i i i或前缀 i − 1 i-1 i1的最长公共后缀长度大于等于 x − 1 x-1 x1的, d p dp dp值大于等于 x − 1 x-1 x1

满足第一个条件的位置在我们线性枚举 d p ( i ) dp(i) dp(i)时是也是线性增加的,用一个指针去扫即可。

在SAM的parent树上,从代表前缀 i i i或代表前缀 i − 1 i-1 i1的节点开始跳父亲,跳到最浅的一个满足 s t e p ( x ) ≥ x − 1 step(x) \geq x-1 step(x)x1的点,然后这个点的子树里的点代表的字符串都符合第二个条件,用dfs序将子树信息转成序列的一段区间。

第三个条件就以dfs序为下标,线段树维护区间最大值即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=1000005;
int n,SZ,last,ans,tot,tim;char S[N];
int ch[N][26],pre[N],step[N],pos[N];
int h[N],ne[N<<1],to[N<<1],f[N][20],in[N],out[N],dp[N],mx[N<<2];

void ins(int x,int id) {
	int np=++SZ,p=last;
	pos[id]=last=np,step[np]=step[p]+1;
	while(p&&!ch[p][x]) ch[p][x]=np,p=pre[p];
	if(!p) pre[np]=1;
	else {
		int q=ch[p][x];
		if(step[q]==step[p]+1) pre[np]=q;
		else {
			int nq=++SZ;step[nq]=step[p]+1;
			for(RI i=0;i<26;++i) ch[nq][i]=ch[q][i];
			pre[nq]=pre[q],pre[q]=pre[np]=nq;
			while(ch[p][x]==q) ch[p][x]=nq,p=pre[p];
		}
	}
}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	in[x]=++tim,f[x][0]=las;
	for(RI i=1;i<=19;++i) f[x][i]=f[f[x][i-1]][i-1];
	for(RI i=h[x];i;i=ne[i]) if(to[i]!=las) dfs(to[i],x);
	out[x]=tim;
}

void chan(int x,int s,int t,int i,int v) {
	if(s==t) {mx[i]=max(mx[i],v);return;}
	int mid=(s+t)>>1;
	if(x<=mid) chan(x,s,mid,i<<1,v);
	else chan(x,mid+1,t,(i<<1)|1,v);
	mx[i]=max(mx[i<<1],mx[(i<<1)|1]);
}
int query(int l,int r,int s,int t,int i) {
	if(!l) return 0;
	if(l<=s&&t<=r) return mx[i];
	int mid=(s+t)>>1,re=0;
	if(l<=mid) re=query(l,r,s,mid,i<<1);
	if(mid+1<=r) re=max(re,query(l,r,mid+1,t,(i<<1)|1));
	return re;
}

int getf(int x,int d) {
	for(RI i=19;i>=0;--i) if(step[f[x][i]]>=d) x=f[x][i];
	return x;
}
int check(int id) {
	int x=getf(pos[id],dp[id]-1),y=getf(pos[id-1],dp[id]-1);
	return query(in[x],out[x],1,tim,1)>=dp[id]-1||
		query(in[y],out[y],1,tim,1)>=dp[id]-1;
}
int main()
{
	scanf("%d",&n),scanf("%s",S+1);
	reverse(S+1,S+1+n);
	last=SZ=1;for(RI i=1;i<=n;++i) ins(S[i]-'a',i);
	for(RI i=2;i<=SZ;++i) add(pre[i],i);
	dfs(1,0);
	for(RI i=1,j=0;i<=n;++i) {
		dp[i]=dp[i-1]+1;
		while(!check(i)) --dp[i],++j,chan(in[pos[j]],1,tim,1,dp[j]);
		ans=max(ans,dp[i]);
	}
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值