【题解】CF1063F String Journey(SA)

【题解】CF1063F String Journey(SA)

好久写SA没有1A了

考虑最优解的形式,显然是\(k,k-1,k-2\dots 1\)

直觉告诉我们从后往前考虑,这样就只用考虑\(k\)\(k+1\)的增量。设\(dp(i,j)=0/1\)且钦定第一个串是\(S(i,i+j-1)\)是否存在"Journey"。

转移枚举一个\(dp(k,j-1)\)然后判断一下\(S(k,k+j-1+1)\)是否是\(S(i,i+j-1)\)的子串即可。注意到因为长度差\(=1\),可以分别枚举\(S(i,i+j-1)\)长度为\(j-1\)的前缀和后缀判等就行。

容易发现固定\(i\)\(dp(i,j)=1\)\(j\)是一段前缀。那么只要记最大的那个就行了,记为\(dp(i)\)(和前面的定义共存)。

转移要枚举一个\(j\)复杂度还是\(\ge O(n^2)\),但我们还能发现,\(dp(i)\le dp(i+1)+1\)。那么枚举\(j\)的复杂度均摊到\(O(2n)\)

这个转移可以用SA优化,因为SA数组上可以转移而来的(满足子串限制的)是一段区间,用线段树维护下\(dp(i)\)的最大值就行。

但是我们忽略了一个要求是\(k>t+j-1\)。本来很难处理,但由于这里\(dp(i)\le dp(i+1)+1\),也就是说\(i+1\)合法的\(k\)恰好\(i\)也合法,而且我们从大到小枚举的\(k\),所以加入过的\(dp(k)\)不需要撤销,线段树搞搞就行了。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;  typedef long long ll;
const int maxn=5e5+5;
char s[maxn];
int n,lg[maxn],st[20][maxn],sa[maxn],rk[maxn],dp[maxn],seg[maxn<<2];

void sufsort(char*str,int*sa,int*rk,int*h,int n){
	static int temp[maxn<<1],buk[maxn];
	for(int k=0,m=128;(1<<k>>1)<=n;++k){
		if(!k) for(int t=1;t<=n;++t) temp[t]=t,temp[t+n]=0,rk[t]=str[t];
		int l=1<<k>>1,p=0,q=l;
		for(int t=1;t<=n;++t){
			if(sa[t]>n-l) temp[++p]=sa[t];
			if(sa[t]>l) temp[++q]=sa[t]-l;
		}
		for(int t=1;t<=n;++t) ++buk[rk[t]];
		for(int t=1;t<=m;++t) buk[t]+=buk[t-1];
		for(int t=n;t;--t) sa[buk[rk[temp[t]]]--]=temp[t];
		memcpy(temp+1,rk+1,n<<2);  memset(buk+1,0,m<<2); rk[sa[1]]=1;
		for(int t=2;t<=n;++t)
			rk[sa[t]]=temp[sa[t-1]]==temp[sa[t]]&&temp[sa[t-1]+l]==temp[sa[t]+l]?rk[sa[t-1]]:rk[sa[t-1]]+1;
		m=rk[sa[n]];
	}
	for(int t=1,p=0;t<=n;++t){
		if(p) --p;
		if(rk[t]!=1) while(str[t+p]==str[sa[rk[t]-1]+p]) ++p;
		else p=0;
		h[rk[t]]=p;
	}
}

int que(int l,int r){
	return min(st[lg[r-l+1]][l],st[lg[r-l+1]][r-(1<<lg[r-l+1])+1]);
}

#define mid ((l+r)>>1)
#define lef l,mid,pos<<1
#define rgt mid+1,r,pos<<1|1
int getL(int pos,int v){
	if(st[0][pos]<v) return pos+1;
	int l=1,r=pos-1;
	do{
		if(que(mid,pos)>=v) r=mid-1;
		else l=mid+1;
	}while(l<=r);
	return l;
}

int getR(int pos,int v){
	if(st[0][pos+1]<v) return pos-1;
	int l=pos+1,r=n;
	do{
		if(que(pos+1,mid)>=v) l=mid+1;
		else r=mid-1;
	}while(l<=r);
	return r;
}

void upd(int p,int v,int l,int r,int pos){
	if(p<l||p>r) return;
	if(l==r) return seg[pos]=v,void();
	if(p<=mid) upd(p,v,lef);
	if(mid<p) upd(p,v,rgt);
	seg[pos]=max(seg[pos<<1],seg[pos<<1|1]);
}

int que(int L,int R,int l,int r,int pos){
	if(L>r||R<l) return 0;
	if(L<=l&&r<=R) return seg[pos];
	return max(que(L,R,lef),que(L,R,rgt));
}

#undef mid 
#undef lef
#undef rgt

int main(){
	cin>>n>>(s+1);
	sufsort(s,sa,rk,st[0],n);
	for(int t=2;t<=n;++t) lg[t]=lg[t>>1]+1;
	for(int t=1;t<=lg[n];++t)
		for(int i=1;i+(1<<t>>1)<=n;++i)
			st[t][i]=min(st[t-1][i],st[t-1][i+(1<<t>>1)]);
	int ans=dp[n]=1;
	for(int t=n,j=2;t;--t,++j){
		while(j){
			if(t+j<=n) upd(rk[t+j],dp[t+j],1,n,1);
			if(que(getL(rk[t],j-1)-1,getR(rk[t],j-1),1,n,1)>=j-1) break;
			if(que(getL(rk[t+1],j-1)-1,getR(rk[t+1],j-1),1,n,1)>=j-1) break;
			--j;
		}
		ans=max(dp[t]=j,ans);
	}
	cout<<ans<<endl;
	return 0;
}


posted @ 2020-06-01 22:39  谁是鸽王  阅读(168)  评论(0编辑  收藏  举报