codeforcs 1063F. String Journey - dp -SAM - 主席树/线段树合并 - 子串定位 - 倍增

题目大意:
给你一个长为 n n n的字符串 S S S,求最大的 k k k,使得能够找出 k k k个不重叠的子串 t 1 … t k t_1\dots t_k t1tk,使得 ∀ i ∈ [ 1 , k ) , ∣ t i ∣ > ∣ t i + 1 ∣ \forall i\in[1,k),|t_i|>|t_{i+1}| i[1,k),ti>ti+1,并且 t i + 1 t_{i+1} ti+1 t i t_i ti的子串。 n ≤ 5 × 1 0 5 , 5 s n\le5\times 10^5,5s n5×105,5s
题解:
首先观察性质。
显然和这种“当前串是上一个的子串然后最大化序列长度”之类的,可以证明一定存在一种最优解,使得每个串都是上一个串的前缀或者后缀(注意都有可能,并不一定全部都是后缀或者前缀,因为证明过程是,例如他不是前缀,我想让他变成前缀就把大的串的前缀砍掉;但是由于他已经是后缀了可能砍掉后两个串相等就gg了),并且进一步的说,可以让相邻两个串的长度差恰好是1,再进一步的说,最后最短的串可以让他长度是1(否则每次切掉那个没用的位置,做若干轮即可)。很快发现答案不超过根号,于是你可以朴素的设f(i,j)表示第i个位置向后延伸j的长度是否可行,枚举下一个位置是 S [ i , i + j − 1 ] S[i,i+j-1] S[i,i+j1]的前缀还是后缀,然后显然下一次出现越早越好,因此直接找到那个位置,用那个转移即可。如果实现优秀可以做到不带log的根号算法。
但是又会发现,f(i,j)作为一个bool数组,其一段前缀是1,之后是0.因此设f(i)表示i这个位置最远能够延伸多少,二分答案做上述过程即可两个log。
显然求下一个位置无论如何都优化不掉了(ba),考虑去掉前面的二分答案。
进一步观察性质,很快发现,假设当前f(i)=j,有两种情况,第一种是下一个位置取了个后缀,那么f(i+1)这个位置就和和下一个位置相等,因此 f ( i + 1 ) ≥ j − 1 f(i+1)\geq j-1 f(i+1)j1;第二种情况是下一个位置是前缀,看似此时 f ( i + 1 ) ≥ j − 2 f(i+1)\geq j-2 f(i+1)j2其实不然,你还是可以选出 j − 1 j-1 j1的答案。因此有 f ( i + 1 ) ≥ f ( i ) − 1 f(i+1)\geq f(i)-1 f(i+1)f(i)1恒成立。注意到 f ( n ) = 1 f(n)=1 f(n)=1,而之前的等价于 f ( i ) ≤ f ( i + 1 ) + 1 f(i)\le f(i+1)+1 f(i)f(i+1)+1,因此用类似倍增数组求height的方法即可做到均摊线性的枚举答案。
求下一个位置只要先在SAM上倍增做子串定位,然后上线段树合并/主席树即可。实测如果实现不好(即你开了倍增数组、ch数组开了26),还写了线段树合并会MLE。一个优化方法是发现倍增数组和ch数组使用时间不重叠可以公用,另一种方法是使用主席树可以卡掉很大的空间常数。这样就时空一个log了。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define ull unsigned lint
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define N 500010
#define SIG 27
#define LOG 20
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
#define S(rt) (((rt)==NULL)?0:((rt)->s))
#define CH(rt,w) (((rt)==NULL)?NULL:((rt)->ch[w]))
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
struct edges{ int to,pre; };
struct segment{ int s;segment *ch[2]; };
int f[N];
int update(segment* &x,segment* y,int l,int r,int p)
{
	x=new segment,x->s=S(y)+1;int mid=(l+r)>>1;
	x->ch[0]=CH(y,0),x->ch[1]=CH(y,1);if(l==r) return 0;
	if(p<=mid) update(x->ch[0],CH(y,0),l,mid,p);
	if(mid<p) update(x->ch[1],CH(y,1),mid+1,r,p);
	return 0;
}
int query(segment* x,segment* y,int l,int r,int p)
{
	int mid=(l+r)>>1,ans;if(S(x)-S(y)==0||p>r) return 0;if(l==r) return r;
	if((ans=query(CH(x,0),CH(y,0),l,mid,p))) return ans;
	return query(CH(x,1),CH(y,1),mid+1,r,p);
}
struct SAM{
	int node_cnt,rt,las,val[N*2],ch[N*2][SIG],dfc,in[N<<1],rp[N<<1];
	int up[N<<1][LOG],fa[N*2],ps[N<<1],n,h[N<<1],etop,out[N<<1],tms[N<<1];
	struct edges { int to,pre; }e[N<<1];segment *seg[N<<1];
	inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
	SAM() { n=node_cnt=0,rt=las=new_SAM_node(0); }
	inline int new_SAM_node(int v,int x=0) { return val[x=++node_cnt]=v,x; }
	inline int extend(int w)
	{
		int p=las,np=new_SAM_node(val[p]+1);
		while(p&&!ch[p][w]) ch[p][w]=np,p=fa[p];
		if(!p) fa[np]=rt;
		else{
			int q=ch[p][w],v=val[p]+1;
			if(val[q]==v) fa[np]=q;
			else{
				int nq=new_SAM_node(v);
				memcpy(ch[nq],ch[q],sizeof ch[q]);
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				while(p&&ch[p][w]==q) ch[p][w]=nq,p=fa[p];
			}
		}
		return las=np,ps[++n]=np,rp[np]=n;
	}
	int dfs(int x)
	{
		in[x]=dfc+1;if(rp[x]) tms[++dfc]=rp[x];
		for(int i=h[x];i;i=e[i].pre) dfs(e[i].to);
		return out[x]=dfc;
	}
	inline int build()
	{
		dfc=0,getedge(),getup(),dfs(rt),seg[0]=NULL;
		rep(i,1,n) update(seg[i],seg[i-1],1,n,tms[i]);
		return 0;
	}
	inline int getedge() { rep(i,1,node_cnt) if(fa[i]) add_edge(fa[i],i);return 0; }
	inline int getup()
	{
		rep(i,1,node_cnt) up[i][0]=fa[i];
		rep(j,1,LOG-1) rep(i,1,node_cnt) up[i][j]=up[up[i][j-1]][j-1];
		return 0;
	}
	inline int getnode(int r,int len)
	{
		int x=ps[r];
		for(int i=LOG-1;i>=0;i--)
			if(up[x][i]&&val[up[x][i]]>=len) x=up[x][i];
		return x;
	}
	inline int query_nxt(int l,int r,int t)//s[q,q+len-1]=s[l,r],q>=t
	{
		int len=r-l+1,x=getnode(r,len);
		if(in[x]>out[x]||t+len-1>n) return 0;
		int ans=query(seg[out[x]],seg[in[x]-1],1,n,t+len-1);
		if(ans) return ans-len+1;return 0;
	}
}s;
inline int cant(int l,int x,int t=0)
{
	t=s.query_nxt(l,l+x-2,l+x);if(t&&f[t]>=x-1) return 0;
	t=s.query_nxt(l+1,l+x-1,l+x);if(t&&f[t]>=x-1) return 0;
	return 1;
}
char str[N];
int main()
{
	int n,ans=0;scanf("%d%s",&n,str+1);rep(i,1,n) s.extend(str[i]-'a');s.build();
	for(int i=n-(f[n]=1);i;i--) for(f[i]=f[i+1]+1;f[i]>1&&cant(i,f[i]);f[i]--);
	rep(i,1,n) ans=max(ans,f[i]);return !printf("%d\n",ans);
}

惨遭MLE的线段树合并代码:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define ull unsigned lint
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define N 500010
#define SIG 27
#define LOG 20
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
struct edges{ int to,pre; };
struct segment{ int s;segment *ch[2]; };
int f[N];
int update(segment* &rt,int p,int l,int r)
{
	int mid=(l+r)>>1;rt=new segment,rt->s=1;
	rt->ch[0]=rt->ch[1]=NULL;if(l==r) return 0;
	if(p<=mid) update(rt->ch[0],p,l,mid);
	if(mid<p) update(rt->ch[1],p,mid+1,r);
	return 0;
}
segment *merge_seg(segment* &x,segment* &y,int l,int r)
{
	if(x==NULL) return y;if(y==NULL) return x;
	segment *z=new segment;z->ch[0]=z->ch[1]=NULL;
	z->s=x->s+y->s;int mid=(l+r)>>1;
	z->ch[0]=merge_seg(x->ch[0],y->ch[0],l,mid);
	z->ch[1]=merge_seg(x->ch[1],y->ch[1],mid+1,r);
	return z;
}
int query(segment* &rt,int l,int r,int p)
{
	if(rt==NULL||rt->s==0||p>r) return 0;
	int mid=(l+r)>>1,ans=0;if(l==r) return rt->s?l:0;
	if((ans=query(rt->ch[0],l,mid,p))) return ans;
	return query(rt->ch[1],mid+1,r,p);
}
struct SAM{
	int node_cnt,rt,las,val[N*2],ch[N*2][SIG];
	int up[N<<1][LOG],fa[N*2],ps[N<<1],n,h[N<<1],etop;
	struct edges { int to,pre; }e[N<<1];segment *seg[N<<1];
	inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
	SAM() { n=node_cnt=0,rt=las=new_SAM_node(0); }
	inline int new_SAM_node(int v,int x=0) { return val[x=++node_cnt]=v,x; }
	inline int extend(int w)
	{
		int p=las,np=new_SAM_node(val[p]+1);
		while(p&&!ch[p][w]) ch[p][w]=np,p=fa[p];
		if(!p) fa[np]=rt;
		else{
			int q=ch[p][w],v=val[p]+1;
			if(val[q]==v) fa[np]=q;
			else{
				int nq=new_SAM_node(v);
				memcpy(ch[nq],ch[q],sizeof ch[q]);
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				while(p&&ch[p][w]==q) ch[p][w]=nq,p=fa[p];
			}
		}
		return las=np,ps[++n]=np;
	}
	inline int build(int x=0)
	{
		if(!x)
		{
			rep(i,1,n) /*debug(ps[i])sp,debug(i)ln,*/update(seg[ps[i]],i,1,n);
			return getedge(),getup(),build(rt);
		}
		for(int i=h[x],y;i;i=e[i].pre)
			build(y=e[i].to),seg[x]=merge_seg(seg[x],seg[y],1,n);
		return 0;
	}
	inline int getedge() { rep(i,1,node_cnt) if(fa[i]) /*debug(fa[i])sp,debug(i)ln,*/add_edge(fa[i],i);return 0; }
	inline int getup()
	{
		rep(i,1,node_cnt) up[i][0]=fa[i];
		rep(j,1,LOG-1) rep(i,1,node_cnt) up[i][j]=up[up[i][j-1]][j-1];
		return 0;
	}
	inline int getnode(int r,int len)
	{
		int x=ps[r];
		for(int i=LOG-1;i>=0;i--)
			if(up[x][i]&&val[up[x][i]]>=len) x=up[x][i];
		return x;
	}
	inline int query_nxt(int l,int r,int t)//s[q,q+len-1]=s[l,r],q>=t
	{
		int len=r-l+1,x=getnode(r,len),ans=query(seg[x],1,n,t+len-1);
		if(ans) return ans-len+1;return 0;
	}
}s;
inline int cant(int l,int x,int t=0)
{
	t=s.query_nxt(l,l+x-2,l+x);if(t&&f[t]>=x-1) return 0;
	t=s.query_nxt(l+1,l+x-1,l+x);if(t&&f[t]>=x-1) return 0;
	return 1;
}
char str[N];
int main()
{
	int n,ans=0;scanf("%d%s",&n,str+1);rep(i,1,n) s.extend(str[i]-'a');s.build();
	for(int i=n-(f[n]=1);i;i--) for(f[i]=f[i+1]+1;f[i]>1&&cant(i,f[i]);f[i]--);
	rep(i,1,n) ans=max(ans,f[i]);return !printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值