zoukankan      html  css  js  c++  java
  • CF700E Cool Slogans

    题目

    题意:给定一个字符串 (S),要求构造字符串序列 (s_1,s_2,ldots,s_k),满足每个 (s_i) 都是 (S) 的子串,且 (forall iin[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_ugets 1()初值())
    • (dp_ugets dp_v+1(v)(u) 的子树内且 (u) 所接收的串在 (v) 所接收的串中出现了两次())

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

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

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

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

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

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

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

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

    有一件我没有想到的事情是,用树上双指针之类的东西来替代倍增,可以 (O(nlog 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
    ",dp[1]);
    	return 0;
    }
    
  • 相关阅读:
    剑指offer【面试题10 :矩形覆盖】
    剑指offer【面试题3 :二维数组中的查找】
    GStreamer Tutorials
    CMake Tutorial
    python
    python
    python-线程、进程、协程
    python-类的继承
    python-操作缓存
    python-线程池
  • 原文地址:https://www.cnblogs.com/Camp-Nou/p/13289474.html
Copyright © 2011-2022 走看看