zoukankan      html  css  js  c++  java
  • 【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;//输出答案
    }
    
  • 相关阅读:
    PHP学习笔记-session
    [C语言] 插入排序之希尔(shell)排序的特性及实现
    [C语言] 插入排序之二分插入排序的特性及实现
    [C语言] 插入排序之直接插入排序的特性及实现
    [Linux环境编程] TCP通信与多线程编程实现“多人在线聊天室”
    [Linux环境编程] 信号的基本概念与操作函数
    [Linux环境编程] Linux系统命令“rm -rf”的实现
    [Linux环境编程] Linux系统命令“ls -R”的实现
    [Linux环境编程] Linux系统命令“ls -l”的实现
    [C语言] 单向链表的构建以及翻转算法_图文详解(附双向链表构建代码)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF1063F.html
Copyright © 2011-2022 走看看