【CF1063F】String Journey(后缀自动机+线段树)

点此看题面

  • 给定一个长度为(n)的字符串。
  • 你可以从左到右选出若干无交子串,要求每次选出的字符串必须是前一次的真子串。
  • 问最多能选出多少个字符串。
  • (nle5 imes10^5)

翻转字符串

方便起见,我们将原串翻转。

那么现在问题就等价于从左到右选出若干无交子串,要求前一次选出的字符串必须是这次的真子串。

贪心地考虑,让选出的字符串长度从(1)开始每次仅增加(1)一定不会变劣(显然),因此我们不如强制选出的第(k)字符串长度就是(k)

动态规划

(f_i)表示以(i)为最后一个串的结尾,最多选出多少个字符串。

显然,如果(f_i)能做到,那么(f_i-1)也必然能做到——只要删去长度为(1)的串以及剩余每个串开头第一个字符即可。同理,实际上任意小于等于(f_i)的答案都能做到。

发现这里有一个基本性质:(f_ile f_{i-1}+1)

证明就是若(f_i)能取到更大的值,则(f_{i-1})也一定能取得更大。

于是,实际上我们不需要考虑如何转移,只需要考虑当前的(f_i)是否能做到,不能做到就将(f_i)(1)

验证(f_i)能做到,就是要判断是否存在(j)满足(j<i-f_i+1,f_jge f_i-1)且前缀(j)长度为(f_i-1)的后缀是前缀(i)长度为(f_i)的后缀的子串。

由于这两个串长度分别是(f_i-1)(f_i),其实也就等价于前缀(j)长度为(f_i-1)的后缀等于前缀(i)长度为(f_i-1)的后缀或者前缀(i-1)长度为(f_{i}-1)的后缀。

整理一下,就是对于前缀(i)长度为(f_i-1)的后缀以及前缀(i-1)长度为(f_i-1)的后缀这两个子串,分别找到最大的满足(j<i-f_i+1)且当前询问子串是前缀(j)的后缀的(f_j),取较大值之后判断是否大于等于(f_i-1)

(f_ile f_{i-1}+1)变形得到(i-f_ige i-1-f_{i-1}),也就是说(i-f_i)是不降的。

那么我们可以只考虑每当将(f_i)减小(1)之后,便将此时的(i-f_i)也看作一个可能的(j),那么(j<i-f_i+1)这一层限制就自然而然没掉了。

剩下的东西可以用后缀自动机来维护了。

后缀自动机

现在的问题就仅仅是对于所有满足当前查询子串是其后缀的前缀(j),求出最大的(f_j)

如果把这个问题搬到后缀自动机上,我们首先通过倍增找到当前查询子串对应的节点。

则当前子串是前缀(j)的一个后缀就充要于它在(parent)树上是(j)的祖先。

也就是说,我们只要给所有(j)对应节点打上一个(f_j)的标记,那么一次询问就相当于是求(parent)树上对应节点的子树最大值。

只要用线段树维护(parent)树的(dfs)序列即可。

代码:(O(nlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define LN 20
using namespace std;
int n,Nt,p[N+5],f[N+5];char s[N+5];
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=Nt,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		int Mx[N<<3];
	public:
		I void U(CI x,CI v,PT)//单点修改
		{
			if(Mx[rt]=max(Mx[rt],v),l==r) return;RI mid=l+r>>1;x<=mid?U(x,v,LT):U(x,v,RT);
		}
		I int Q(CI L,CI R,PT)//区间求最大值
		{
			if(L<=l&&r<=R) return Mx[rt];RI mid=l+r>>1;return max(L<=mid?Q(L,R,LT):0,R>mid?Q(L,R,RT):0);
		}
}T;
class SuffixAutomation
{
	private:
		#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
		int lst;struct node {int L,F[LN+5],S[30];}O[N<<1];
		int d,dI[N<<1],dO[N<<1],ee,lnk[N<<1];struct edge {int to,nxt;}e[N<<1];
		I void dfs(CI x)//dfs预处理,记录dfs序
		{
			dI[x]=++d;for(RI i=1;i<=LN;++i) O[x].F[i]=O[O[x].F[i-1]].F[i-1];//顺带预处理倍增数组
			for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to);dO[x]=d;
		}
	public:
		I SuffixAutomation() {Nt=lst=1;}I int Ins(CI x)//插入一个字符,返回对应节点编号
		{
			RI p=lst,o=lst=++Nt;O[o].L=O[p].L+1;
			W(p&&!O[p].S[x]) O[p].S[x]=o,p=O[p].F[0];if(!p) return O[o].F[0]=1,o;
			RI q=O[p].S[x];if(O[p].L+1==O[q].L) return O[o].F[0]=q,o;
			RI k=++Nt;(O[k]=O[q]).L=O[p].L+1,O[o].F[0]=O[q].F[0]=k;
			W(p&&O[p].S[x]==q) O[p].S[x]=k,p=O[p].F[0];return o;
		}
		I void Init() {for(RI i=1;i<=Nt;++i) add(O[i].F[0],i);dfs(1);}//给parent树连边,然后dfs
		I void U(CI x,CI v) {T.U(dI[x],v);}//单点修改
		I int Q(RI x,CI k)//询问前缀x长度为k的后缀
		{
			RI i;for(i=LN;~i;--i) O[O[x].F[i]].L>=k&&(x=O[x].F[i]);return T.Q(dI[x],dO[x]);//倍增找到对应节点,然后子树询问
		}
}S;
int main()
{
	RI i;for(scanf("%d%s",&n,s+1),reverse(s+1,s+n+1),i=1;i<=n;++i) p[i]=S.Ins(s[i]&31);S.Init();//预处理
	#define Check(i) (max(S.Q(p[i],f[i]-1),S.Q(p[i-1],f[i]-1))>=f[i]-1)//检验当前f[i]是否可行
	RI t=1;for(f[1]=1,i=2;i<=n;t=max(t,f[i++]))//枚举每一位DP,t统计答案
		{f[i]=f[i-1]+1;W(f[i]^1&&!Check(i)) --f[i],S.U(p[i-f[i]],f[i-f[i]]);}//不行就将f[i]减1,同时将i-f[i]视作可能的j
	return printf("%d
",t),0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF1063F.html