CF700E Cool Slogans

题目

题意:给定一个字符串 \(S\),要求构造字符串序列 \(s_1,s_2,\ldots,s_k\),满足每个 \(s_i\) 都是 \(S\) 的子串,且 \(\forall i\in[2,k]\)\(s_i\)\(s_{i-1}\) 中出现了至少 \(2\) 次,使得 \(k\) 最大。

字符串题也挺有意思的。

结论 0:必定存在一种最优方案,使字符串序列中 \(s_i\)\(s_{i-1}\) 的后缀。

证明显然。

考虑一个 naive 的 DP:\(dp_{l,r}\) 表示以 \(S[l:r]\) 为最短字符串的最长序列。根据那个结论,转移时只要找到所有 \(S[l,r]=S[l',r'](l'<l)\),然后 \(dp_{l,r}\leftarrow dp_{l',r}+1\) 即可。

这个复杂度有点大,那我们把 SAM 建出来看看。

那我们设 \(dp_u\),表示以结点 \(u\) 所接收的子串为最短字符串的最长序列,那么 \(dp_u\) 很明显可以从 Parent 树里 \(u\) 的子树中的某些 \(dp_v\) 转移来。具体地:

  • \(dp_u\gets 1(\)初值\()\)
  • \(dp_u\gets dp_v+1(v\)\(u\) 的子树内且 \(u\) 所接收的串在 \(v\) 所接收的串中出现了两次\()\)

其中第二个转移看起来比较难处理。

我们考虑一个 \(v\) 对它的哪些祖先有贡献,发现只要 \(v\) 对祖先 \(u\) 有贡献,它对 \(u\) 的所有祖先也都有贡献了。

这是一个单调性,所以我们可以用一个倍增来找到最近的 \(u\)

那如何判断出现了两次呢?

考虑 \(v\) Right 集合中随便一个位置 \(p\),找到 \(u\) Right 集合中小于 \(p\) 的最大位置 \(q\),那么出现了两次当且仅当 \(q-len_u\ge p-len_v\)

相当于问 \(u\) 的子树内,小于 \(p\)\(q\) 当中,最大的有没有达到 \(p-len_v+len_u\)。DFS 序 + 可持久化线段树即可(当然也可以 可持久化可合并线段树 这样子)。

找到了最近的 \(u\),那剩下的祖先没转移到啊?加一个新转移 \(dp_u\gets dp_v(v\)\(u\) 的儿子\()\) 就可以了。

倍增一个 log,线段树查询一个 log,总复杂度 \(O(n\log^2 n)\)

有一件我没有想到的事情是,用树上双指针之类的东西来替代倍增,可以 \(O(n\log n)\)


可是我们刚刚的做法其实包含着两个结论没有证明。

结论 1:若存在 \(u\) 所接收的串 \(s_u\)\(v\) 所接收的串 \(s_v\) 中出现了两次,那么,对于 \(u\) 所接收的任何串 \(s'_u\),存在一个 \(v\) 所接收的串 \(s'_v\)\(s'_u\)\(s'_v\) 中出现了两次。

结论 2:同一个结点 \(u\) 所接收的所有子串 答案相等。

这两个结论证明应该不难,就不写了。

#include<cstdio>
#include<algorithm>
const int N=2e5+3,K=20;
struct edge{int v,nxt;}g[N+N];
int n,fa[N+N][K],son[N+N][26],len[N+N],pos[N+N],lst,t,head[N+N],k,dfn[N],ord[N],dfn2[N+N],siz[N+N],dfc,dp[N+N];
char a[N];
inline void Insert(int u,int v){g[++k]=(edge){v,head[u]};head[u]=k;}
void Dfs0(int u){
	int v;
	for(int j=1;j<K;j++)fa[u][j]=fa[fa[u][j-1]][j-1];
	dfn2[u]=dfc+1;
	if(pos[u]>0)
	  ord[dfn[pos[u]]=++dfc]=pos[u],siz[u]=1;
	for(int i=head[u];i;i=g[i].nxt){
	  v=g[i].v;
	  Dfs0(v);
	  siz[u]+=siz[v];
	}
}
#define M (L+R>>1)
struct segment_tree{
	int mx[N*K],ls[N*K],rs[N*K],t,rt[N];
	int Max(int l,int r,int L,int R,int k){
		if(!k||l>r)return 0;
		if(l<=L&&R<=r)return mx[k];
		if(r<=M)return Max(l,r,L,M,ls[k]);
		if(l> M)return Max(l,r,M+1,R,rs[k]);
		return std::max(Max(l,r,L,M,ls[k]),Max(l,r,M+1,R,rs[k]));
	}
	void Update(int p,int a,int L,int R,int k0,int&k1){
		k1=++t,ls[k1]=ls[k0],rs[k1]=rs[k0];
		mx[k1]=std::max(mx[k0],a);
		if(L==R)return;
		p<=M?Update(p,a,L,M,ls[k0],ls[k1]):Update(p,a,M+1,R,rs[k0],rs[k1]);
	}
}tr;
void Dfs1(int u){
	int v,w;
	for(int i=head[u];i;i=g[i].nxt){
	  v=g[i].v,Dfs1(v);
	  dp[u]=std::max(dp[u],dp[v]);
	  pos[u]=pos[v];
	}
	v=u;
	for(int j=K-1;~j;j--)
	  if((w=fa[v][j])&&tr.Max(dfn2[w],dfn2[w]+siz[w]-1,1,n,tr.rt[pos[u]-1])-len[w]<pos[u]-len[u])
		v=w;
	if(fa[v][0]>1)dp[fa[v][0]]=std::max(dp[fa[v][0]],dp[u]+1);
}
int main(){
	int u,v,w,p,c;
	scanf("%d%s",&n,a+1);
	lst=t=1;
	for(int i=1;i<=n;i++){
	  c=a[i]-'a';
	  u=++t,len[u]=i,pos[u]=i;
	  for(v=lst;v&&!son[v][c];v=fa[v][0])son[v][c]=u;
	  if(v){
		p=son[v][c];
		if(len[p]==len[v]+1)fa[u][0]=p;
		else{
		  w=++t,len[w]=len[v]+1;
		  fa[w][0]=fa[p][0],fa[u][0]=fa[p][0]=w;
		  for(int j=0;j<26;j++)son[w][j]=son[p][j];
		  for(;v&&son[v][c]==p;v=fa[v][0])son[v][c]=w;
		}
	  }else fa[u][0]=1;
	  lst=u;
	}
	for(u=2;u<=t;u++)Insert(fa[u][0],u);
	Dfs0(1);
	for(int i=1;i<=n;i++)tr.Update(dfn[i],i,1,n,tr.rt[i-1],tr.rt[i]);
	for(u=1;u<=t;u++)dp[u]=1;
	Dfs1(1);
	printf("%d\n",dp[1]);
	return 0;
}

posted on 2020-07-12 19:19  Dreamunk  阅读(179)  评论(0)    收藏  举报

导航