zoukankan      html  css  js  c++  java
  • CF700E Cool Slogans 和 CF1063F String Journey

    CF700E Cool Slogans

    给定一个字符串 (S),要求构造字符串序列 (s_1,s_2,ldots,s_k),满足任意 (s_i) 都是 (S) 的子串,且任意 (iin[2,n]),都有 (s_i)(s_{i-1}) 中出现了至少 (2) 次(可以有重叠部分,只要起始、结尾位置不同即可)。 求可能的最大的 (k) 的值(即序列的最大可能长度)。

    (1leq nleq 2 imes 10^5)

    题解

    https://www.luogu.com.cn/blog/xht37/solution-cf700e

    对字符串建 SAM,用可持久化线段树合并求出每个节点的 (operatorname{endpos}) 集合。

    (operatorname{parent}) 树上从根向下 dp,设 (f_i) 表示到节点 (i) 时的最大值。

    如果一个父节点的子串在子节点的子串中出现了至少两次,则转移时 (f) 加一,否则不变。

    考虑如何判断是否出现了至少两次,设此时的子节点为 (x),父节点为 (pa_x)

    找到 (x) 对应的 (operatorname{endpos}) 中的任意一个位置 (pos),则 (pos)(pa_x) 的子串一定出现了一次。

    那么另一次只要在 ([pos - operatorname{len}(x) + operatorname{len}(pa_x), pos - 1]) 中有出现就行了。

    总时间复杂度 (mathcal O(n log n))

    CO int N=4e5+10;
    namespace seg{
    	int tot,lc[N*20],rc[N*20];
    	
    	#define mid ((l+r)>>1)
    	void insert(int&x,int l,int r,int p){
    		if(!x) x=++tot;
    		if(l==r) return;
    		if(p<=mid) insert(lc[x],l,mid,p);
    		else insert(rc[x],mid+1,r,p);
    	}
    	int merge(int x,int y){
    		if(!x or !y) return x+y;
    		int z=++tot;
    		lc[z]=merge(lc[x],lc[y]);
    		rc[z]=merge(rc[x],rc[y]);
    		return z;
    	}
    	bool query(int x,int l,int r,int ql,int qr){
    		if(!x) return 0;
    		if(ql<=l and r<=qr) return 1;
    		if(qr<=mid) return query(lc[x],l,mid,ql,qr);
    		if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
    		return query(lc[x],l,mid,ql,qr) or query(rc[x],mid+1,r,ql,qr);
    	}
    	#undef mid
    }
    
    int last=1,tot=1;
    array<int,26> ch[N];
    int fa[N],len[N],pos[N],root[N];
    
    void extend(int c,int p){
    	int x=last,cur=last=++tot;
    	len[cur]=len[x]+1,pos[cur]=p;
    	for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur;
    	if(!x) {fa[cur]=1; return;}
    	int y=ch[x][c];
    	if(len[y]==len[x]+1) {fa[cur]=y; return;}
    	int clone=++tot;
    	ch[clone]=ch[y],fa[clone]=fa[y];
    	len[clone]=len[x]+1,pos[clone]=p;
    	fa[cur]=fa[y]=clone;
    	for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
    }
    
    char str[N];
    int cnt[N],ord[N],F[N],G[N];
    
    int main(){
    	int n=read<int>();
    	scanf("%s",str+1);
    	for(int i=1;i<=n;++i){
    		extend(str[i]-'a',i);
    		seg::insert(root[last],1,n,i);
    	}
    	for(int x=1;x<=tot;++x) ++cnt[len[x]];
    	for(int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
    	for(int x=tot;x>=1;--x) ord[cnt[len[x]]--]=x;
    	for(int i=tot;i>=2;--i){
    		int x=ord[i],f=fa[x];
    		root[f]=seg::merge(root[f],root[x]);
    	}
    	int ans=1;
    	for(int i=2;i<=tot;++i){
    		int x=ord[i],f=fa[x];
    		if(f==1) {F[x]=1,G[x]=x; continue;}
    		if(seg::query(root[G[f]],1,n,pos[x]-len[x]+len[G[f]],pos[x]-1))
    			F[x]=F[f]+1,G[x]=x;
    		else F[x]=F[f],G[x]=G[f];
    		ans=max(ans,F[x]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    CF1063F String Journey

    给定一个长度为(n)的只包含小写英文字母的字符串(s) ,你需要找到一个最大的(k) ,使得存在:

    [1le l_1le r_1< l_2le r_2< dots < l_kle r_kle n ]

    (即(k)个区间([l_1,r_1],[l_2,r_2]dots [l_k,r_k])的左右端点都递增且两两不相交)

    使得对于每个(1le i < k) ,都满足(s[l_{i+1}dots r_{i+1}])(s[l_idots r_i])的严格子串。

    (1le nle 5 imes 10^5),字符串仅包含小写字母。

    题解

    https://www.cnblogs.com/xyz32768/p/12230395.html

    • 性质1:存在一种最优方案,使得最后一段长为(1)且选出的每段长都比上一段小(1)

      证明:如果某一段比下一段长(2)或以上,则显然可以在这一段的前面或后面去掉若干字符,只需保证下一段是其严格子串,最后一段长为(1)的证明同理

      于是我们发现从右往左DP更有拓展性。

    • 性质2:答案不超过(O(sqrt n))

      证明:最坏情况下是(1 + 2 + 3 + dots + k),这时必然有(frac{k(k+1)}{2}leq n)

    • 性质3:以(i)为第一个串的开头,如果首串长为(j)时能选出(j)段,那么首串长为(j − 1)时也一定能选出(j − 1)段。

      证明:可以用下面的图直观理解,红色表示(j)变成(j − 1)删去的部分。

      1

    于是我们可以把状态改成一维:(f[i])表示首串的开头为(i)最多能选出多少段。

    (f[i])可以先二分答案( ext{mid})

    然后现在要找的就是([i + ext{mid}, n])内是否存在一个j满足:

    1. (f[j] ≥ ext{mid} − 1)

    2. (operatorname{lcp}(i,j) ≥ ext{mid} − 1)(operatorname{lcp}(i + 1,j) ≥ ext{mid} − 1)

    注意到如果我们求出了原串的后缀数组,那么(operatorname{lcp}(i,j) ≥ ext{mid} − 1)相当于( ext{rank}_j)在某个区间内,(operatorname{lcp}(i + 1,j)≥ ext{mid} − 1)也是一样,这个区间可以用二分+RMQ求出。

    问题转化成求(j in [i + ext{mid}, n])且满足( ext{rank}_j)在某区间内的(f[j])最大值。

    可持久化线段树即可维护,(O(n log^2 n))

    • 性质4:(f[i] ≤ f[i + 1]+ 1)

      证明类似性质3,即对于一个首串从(i)开始,有(j)个串的方案,如果删掉首串的第一个位置,那么一定可以用类似性质3的调整法得到一个从(i + 1)开始,有(j − 1)个串的方案。

      2

    于是我们可以把二分改成先让(f[i] leftarrow f[i + 1]+ 1)之后,判断当前是否存在选出(f[i])个串的方案,如果不行就让(f[i])一直减一,直到合法为止。
    类似于SA求( ext{height}),可以得出总复杂度为(O(n log n))

    CO int N=1e6+10;
    char str[N];
    int last=1,tot=1;
    array<int,26> ch[N];
    int fa[N],len[N],pos[N],val[N],idx[N];
    
    void extend(int c,int p){
    	int x=last,cur=last=++tot;
    	len[cur]=len[x]+1,pos[cur]=p,idx[p]=cur;
    	for(;!ch[x][c];x=fa[x]) ch[x][c]=cur;
    	if(!x) {fa[cur]=1; return;}
    	int y=ch[x][c];
    	if(len[y]==len[x]+1) {fa[cur]=y; return;}
    	int clone=++tot;
    	ch[clone]=ch[y],fa[clone]=fa[y],len[clone]=len[x]+1,pos[clone]=pos[cur];
    	fa[cur]=fa[y]=clone;
    	for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
    }
    
    array<int,26> son[N];
    int anc[N][20],L[N],R[N],dfn;
    
    void dfs(int x){
    	L[x]=idx[pos[x]]==x?++dfn:dfn+1;
    	for(int i=1;i<=19;++i) anc[x][i]=anc[anc[x][i-1]][i-1];
    	for(int c=0;c<26;++c)if(son[x][c]){
    		anc[son[x][c]][0]=x;
    		dfs(son[x][c]);
    	}
    	R[x]=dfn;
    }
    int find(int x,int l){
    	for(int i=19;i>=0;--i)
    		if(len[anc[x][i]]>=l) x=anc[x][i];
    	return x;
    }
    
    namespace seg{
    	int root[N],tot;
    	int lc[N*10],rc[N*10],val[N*10];
    	
    	#define mid ((l+r)>>1)
    	void insert(int&x,int l,int r,int p,int v){
    		++tot,lc[tot]=lc[x],rc[tot]=rc[x];
    		val[tot]=max(val[x],v),x=tot;
    		if(l==r) return;
    		if(p<=mid) insert(lc[x],l,mid,p,v);
    		else insert(rc[x],mid+1,r,p,v);
    	}
    	int query(int x,int l,int r,int ql,int qr){
    		if(!x) return 0;
    		if(ql<=l and r<=qr) return val[x];
    		if(qr<=mid) return query(lc[x],l,mid,ql,qr);
    		if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
    		return max(query(lc[x],l,mid,ql,qr),query(rc[x],mid+1,r,ql,qr));
    	}
    	#undef mid
    }
    
    int F[N];
    
    int main(){
    	int n=read<int>();
    	scanf("%s",str+1);
    	for(int i=n;i>=1;--i) extend(str[i]-'a',i);
    	for(int i=tot;i>=2;--i) son[fa[i]][str[pos[i]+len[fa[i]]]-'a']=i;
    	dfs(1);
    	function<bool(int,int)> check=[&](int i,int l)->bool{
    		int x=idx[i],f=find(x,l-1);
    		if(seg::query(seg::root[i+l],1,dfn,L[f],R[f])>=l-1) return 1;
    		x=idx[i+1],f=find(x,l-1);
    		if(seg::query(seg::root[i+l],1,dfn,L[f],R[f])>=l-1) return 1;
    		return 0;
    	};
    	int ans=0;
    	for(int i=n;i>=1;--i){
    		F[i]=F[i+1]+1;
    		while(!check(i,F[i])) --F[i];
    		seg::insert(seg::root[i]=seg::root[i+1],1,dfn,L[idx[i]],F[i]);
    		ans=max(ans,F[i]);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    va_list/va_start/va_arg/va_end深入分析【转】
    Linux Kernel中断子系统来龙去脉浅析【转】
    Linux系统调用---同步IO: sync、fsync与fdatasync【转】
    Linux中变量#,#,@,0,0,1,2,2,*,$$,$?的含义【转】
    linux下的module_param()解释【转】
    Makefile 使用总结【转】
    FLASH的知识【转】
    Linux MTD系统剖析【转】
    linux的mtd架构分析【转】
    linux设备树笔记__dts基本概念及语法【转】
  • 原文地址:https://www.cnblogs.com/autoint/p/12662981.html
Copyright © 2011-2022 走看看