CF700E Cool Slogans——后缀自动机,神仙结论与线段树合并优化 dp

Description

传送门

Solution

Lemma 1

若钦定 s i ( i < k ) s_i(i<k) si(i<k) s i + 1 s_{i+1} si+1 的后缀,答案不变。

Prove 1

假设 s i s_i si 不是 s i + 1 s_{i+1} si+1 的后缀,而只是 s i + 1 s_{i+1} si+1 中由第 l l l 到第 r r r 个字符形成的子串,那么我们完全可以将 s i + 1 s_{i+1} si+1 中的第 r + 1 , r + 2 , ⋯ r+1,r+2,\cdots r+1,r+2, 个字符全部删去,那么我们就在保证了 ( s 1 , s 2 , ⋯   , s k ) (s_1,s_2,\cdots,s_k) (s1,s2,,sk) 依然是一组合法的字符串 k k k 元组的前提下,使满足 s i s_i si 不是 s i + 1 s_{i+1} si+1 后缀的 i i i 的数量减小了 1 1 1

不断操作下去,直到上述值变为 0 0 0,我们就将原 k k k 元组等价地转化为了一个满足所有的 s i ( i < k ) s_i(i<k) si(i<k) 均为 s i + 1 s_{i+1} si+1 后缀的新 k k k 元组。证毕。

Lemma 2

建出字符串 S S S 的后缀自动机,则对于 SAM 上的节点 u u u,若其对应的 S S S 中的若干个子串分别为 t 1 , t 2 , ⋯   , t l t_1,t_2,\cdots,t_l t1,t2,,tl,则其中不存在 p , q ( 1 ≤ p , q ≤ l ) p,q(1 \le p,q \le l) p,q(1p,ql) 使得 t p t_p tp t q t_q tq 中出现超过 1 1 1 次。

Prove 2

考虑反证,假设对于 ( p 0 , q 0 ) (p_0,q_0) (p0,q0) 满足 t p 0 t_{p_0} tp0 t q 0 t_{q_0} tq0 中出现超过 1 1 1 次。

那么不难发现, t p 0 t_{p_0} tp0 的 endpos 集合大小必然大于 t q 0 t_{q_0} tq0 的 endpos 集合大小,这与 SAM 的性质相矛盾。证毕。


根据前两个引理,我们考虑建出 S S S 的后缀自动机,那么 s 1 , s 2 , ⋯   , s k s_1,s_2,\cdots,s_k s1,s2,,sk 所对应的节点就是一条从上至下的链,且 SAM 的每个节点处至多只有一个 s i s_i si

考虑 dp \text{dp} dp

f u f_u fu 表示,从根到 u u u 所组成的最长合法链的长度。其中,一条链 t 1 → t 2 → ⋯ → t x t_1 \to t_2 \to \cdots \to t_x t1t2tx 合法,当且仅当 ∀ i < x \forall i < x i<x 均满足 t i t_i ti 中被选定的链在 t i + 1 t_{i+1} ti+1 中被选定的链的出现次数超过 1 1 1 次。

注意到,因为 SAM 上的一个节点会对应若干个子串,而上述判断链是否合法要涉及到具体的子串,那么在 dp \text{dp} dp 的过程中,我们不仅得维护走到的节点编号,还要维护选了该节点对应的哪个子串,从而 dp \text{dp} dp 的状态数目会达到 O ( ∣ S ∣ 2 ) O(|S|^2) O(S2) 级别。需要优化。

Key Observation

神仙结论,也是本题的核心。

引理给出,对于节点 u , v u,v u,v 而言,若 u u u v v v 的祖先且 v v v 对应的最长串为 B B B,那么对于每个 u u u 对应的 S S S 的子串 A A A A A A B B B 中出现的位置所组成的集合均完全相同。

Key Prove

某位神仙的证明

其实就是他写得太好了并且我懒得复述仅此而已罢了


根据 Key Observation,我们可以得到结论:假设选定了 SAM 中由上至下的节点 t 1 , t 2 , ⋯   , t x t_1,t_2,\cdots,t_x t1,t2,,tx,那么 ∀ i \forall i i,都必定会选定 t i t_i ti 对应的字符串中串长最大的那一个。

这样一来,我们就可以写出 dp \text{dp} dp 转移式了:

f u = max ⁡ OK ( v , u ) = 1 { f v } + 1 f_u=\max_{\text{OK}(v,u)=1} \{f_v\}+1 fu=OK(v,u)=1max{fv}+1

其中 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 当且仅当 v v v 对应的最长串在 u u u 对应的最长串中至少出现了 2 2 2 次。

直接转移的话,时间复杂度是 O ( m 2 ) O(m^2) O(m2) 的,考虑优化。

首先,不难注意到 f u f_u fu 不小于 f f a u f_{fa_u} ffau,其中 f a u fa_u fau 表示 u u u 在 SAM 上的父亲节点。再根据转移式,很容易发现 f u f_u fu 只能等于 f f a u f_{fa_u} ffau f f a u + 1 f_{fa_u}+1 ffau+1。从而,我们只需要判断是否存在一个满足 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 v v v 使得 f v = f f a u f_v=f_{fa_u} fv=ffau 就好了。

不难发现,这些节点都挤在了从 1 1 1 f a u fa_u fau 的链上的末端 x → f a u x \to fa_u xfau。其次,不难注意到满足 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 v v v 必然是从 1 1 1 f a u fa_u fau 的链的顶上若干个点。从而,我们只要判断 x x x 是否满足 OK ( x , u ) = 1 \text{OK}(x,u)=1 OK(x,u)=1 就行了。

于是,问题转化为: 给定两个点 x , u x,u x,u,判断 OK ( x , u ) \text{OK}(x,u) OK(x,u) 是否为 1 1 1。不难发现,这等价于查询一个子树内有多少个前缀点(即建树时维护一段前缀的 SAM 上的点)使得其维护的前缀编号落在某一区间内,更进一步的,这等价于一个强制在线的二维数点问题。使用主席树或预先可持久化线段树合并即可。

综上所述,我们通过挖掘若干个神奇结论,之后在后缀自动机上套用线段树一族的数据结构解决了这道神仙题。时间复杂度线性对数。

Code

挺好写的,但是 WA 了好几次。

#include <bits/stdc++.h>
#pragma GCC optimize("Ofast")
using namespace std;
const int maxl=400005,maxg=33;

int n,res;
int fa[maxl],is_pre[maxl],len[maxl],le[maxl],ri[maxl],dep[maxl],root[maxl];
vector<int> GA[maxl];

namespace PR{
	int tot=0;
	struct President_tree{int ls,rs,sum;}tree[maxl*maxg];
	void pushup(int rt){tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;}
	int clone(int rt){tree[++tot]=tree[rt];return tot;}
	int build_tree(int l,int r,int rt){
		if (!rt)  rt=(++tot);
		if (l==r)  return rt;
		
		int mid=(l+r)>>1;
		tree[rt].ls=build_tree(l,mid,tree[rt].ls);
		tree[rt].rs=build_tree(mid+1,r,tree[rt].rs);
		return rt;
	}
	int change(int nl,int l,int r,int rt,int k){
		rt=clone(rt);
		if (l==r) {tree[rt].sum++;return rt;}
		
		int mid=(l+r)>>1;
		if (nl<=mid)  tree[rt].ls=change(nl,l,mid,tree[rt].ls,k);
		else tree[rt].rs=change(nl,mid+1,r,tree[rt].rs,k);
		
		pushup(rt);
		return rt;
	}
	int query(int nl,int nr,int l,int r,int rt){
		if (nl<=l&&r<=nr)  return tree[rt].sum;
		
		int mid=(l+r)>>1,res=0;
		if (nl<=mid)  res=query(nl,nr,l,mid,tree[rt].ls);
		if (nr>mid)  res+=query(nl,nr,mid+1,r,tree[rt].rs);
		return res;
	}
	int Query(int l,int r,int x,int y){return query(x,y,1,n,root[r])-query(x,y,1,n,root[l-1]);}
	bool ok_trans(int u,int v){return (u==1||Query(le[u],ri[u],is_pre[v]-len[v]+len[u],is_pre[v])>=2);}
}
//SAM
namespace SAM{
	int T=0,las=1,tot=1,t[maxl][26],lis[maxl];
	void insert(int c,int id){
		int p=las,np=(las=(++tot));len[np]=len[p]+1,is_pre[np]=id;
		for (;p&&!t[p][c];p=fa[p])  t[p][c]=np;
		
		if (!p)  fa[np]=1;
		else{
			int q=t[p][c];
			if (len[q]==len[p]+1)  fa[np]=q;
			else{
				int nq=(++tot);fa[nq]=fa[q],fa[np]=fa[q]=nq,len[nq]=len[p]+1;
				for (int i=0;i<26;i++)  t[nq][i]=t[q][i];
				for (;p&&t[p][c]==q;p=fa[p])  t[p][c]=nq;
			}
		}
	}
	void build_graph(){
		for (int i=1;i<=tot;i++)  GA[fa[i]].push_back(i);
	}
	void dfs1(int u){
		dep[u]=dep[fa[u]]+1;
		if (is_pre[u])  le[u]=ri[u]=(++T),lis[T]=is_pre[u];
		else le[u]=n+1,ri[u]=0;
		
		for (auto v:GA[u]){
			dfs1(v);
			le[u]=min(le[u],le[v]),ri[u]=max(ri[u],ri[v]);
			if (is_pre[v])  is_pre[u]=is_pre[v];
		}
	}
	void init_segment_tree(){
		root[0]=PR::build_tree(1,n,1);
		for (int i=1;i<=n;i++)  root[i]=PR::change(lis[i],1,n,root[i-1],1);
	}
	void work(){build_graph(),dfs1(1),init_segment_tree();}
}
//mainwork
namespace ducati{
	int f[maxl],nxt[maxl];
	void get_all_in(){
		cin>>n;
		for (int i=1;i<=n;i++){
			char x;cin>>x;
			SAM::insert(x-'a',i);
		}
		SAM::work();
	}
	void DP(int u,int x){
		f[u]=f[fa[u]];
		if (PR::ok_trans(x,u))  f[u]++,x=u,res=max(res,f[u]);
		
		for (auto v:GA[u])  DP(v,x);
	}
	void work(){
		for (auto u:GA[1])  nxt[1]=u,DP(u,1);
		cout<<res<<endl;
	}
	void solve(){get_all_in(),work();}
}
signed main(){ducati::solve();return 0;}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值