1. 程式人生 > 實用技巧 >「CF1063F」String Journey 題解

「CF1063F」String Journey 題解

首先隨便就能看出來那個樣例是來騙人的。我們要答案最優,顯然 \(|t_k|+1=|t_{k-1}|\)

證明:如果 \(|t_{k-1}|\neq |t_k|+1\),那麼根據 \(\operatorname{Journey}\) 的定義,\(|t_{k-1}| >|t_k|+1\)。這樣可能會佔用 \(|t_{k-2}|\) 的某一個字元,顯然不優。

定義 \(dp_i\) 為以 \(i\) 為字尾的起步,最長的 \(\operatorname{Journey}\) 的長度。列舉轉移點 \(j\),顯然如果 \(j\) 合法,令 \(k=\min(j-i,dp_j+1)\)\(dp_i = k\)

。根據 \(\operatorname{Journey}\) 的定義,應該滿足:

\[S[i \dots i+k-2]=S[j\dots j+k-2] \ ∨\ S[i+1 \dots i+k-1]=S[j\dots j+k-2] \]

這個過程可以使用 hash 完成。此時我們已經得到了一個很搞笑的演算法了。

哦不可是這個毒瘤出題人要求我們用更高效的演算法去做才能夠對得起這個 \(3200\) 的評分。分析一下性質,通過打表我們也能得到:

\[dp_i \leq dp_{i+1}+1 \]

畫圖可以理解:\(dp_i\) 最終指向的位置一定不會超過 \(dp_{i+1}\) 指向的位置。

這個意思是什麼呢?我們定義一個指標 \(p\)

,表示最遠能夠轉移的邊界。根據上面的性質可以得到 \(p\) 單調不增

看似麻煩了,因為現在我們要轉移 dp 必須要滿足兩個條件:

  • \(S[i \dots p-1]=S[j\dots j+p-i-1] \ ∨\ S[i+1 \dots p]=S[j \dots j+p-i-1]\)
  • \(dp_j \geq dp_i+1\)

滿足這兩個條件即可轉移。如果無法達到就 \(p ← p-1\),再次進行判斷。

道理我都懂,如何判斷呢?考慮用字尾陣列維護,求出 height 陣列,根據定義可以用二分在這個陣列上面找到一個滿足這個區間內所有元素 \(k\)\(i\)\(\operatorname{LCP}\)

長度都大於等於 \(p-i\) 的區間。現在我們要求是否 \(\exists j>p:dp_j \geq p-i\)。這個時候我們可以用線段樹去維護這個最大值。每次挪動指標 \(p\) 的時候就將 \(dp\) 值加入,繼續下一輪判斷。

#include<bits/stdc++.h>
#define lc(x) (x<<1)
#define rc(x) ((x<<1)|1)
#define Mm int mid=(l+r)>>1
using namespace std;
char a[500005];
int sa[500005],height[500005],cnt[500005],x[500005],tmp1[500005],Rank[500005],st[500005][21],lg[500005],maxn[2000005],dp[500005],n,m;
void MakeSuffixArray()
{
	m=127;
	for(int i=1;i<=n;++i)	++cnt[x[i]=a[i]];
	for(int i=2;i<=m;++i)	cnt[i]+=cnt[i-1];
	for(int i=n;i;--i)	sa[cnt[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1)
	{
		int tmp=0;
		for(int i=n-k+1;i<=n;++i)	tmp1[++tmp]=i;
		for(int i=1;i<=n;++i)	if(sa[i]>k)	tmp1[++tmp]=sa[i]-k;
		for(int i=1;i<=m;++i)	cnt[i]=0;
		for(int i=1;i<=n;++i)	++cnt[x[i]];
		for(int i=2;i<=m;++i)	cnt[i]+=cnt[i-1];
		for(int i=n;i;--i)	sa[cnt[x[tmp1[i]]]--]=tmp1[i],tmp1[i]=0;
		swap(x,tmp1);
		tmp=1;
		x[sa[1]]=1;
		for(int i=2;i<=n;++i)	x[sa[i]]=(tmp1[sa[i]]==tmp1[sa[i-1]] && tmp1[sa[i]+k]==tmp1[sa[i-1]+k])?tmp:++tmp;
		if(tmp==n)	break;
		m=tmp; 
	}
}
void MakeHeightArray()
{
	int k=0;
	for(int i=1;i<=n;++i)	Rank[sa[i]]=i;
	for(int i=1;i<=n;++i)
	{
		if(Rank[i]==1)	continue;
		if(k)	--k;
		int j=sa[Rank[i]-1];
		while(j+k<=n && i+k<=n && a[i+k]==a[j+k])	++k;
		height[Rank[i]]=k;
	}
}
int query_st(int l,int r)
{
	if(l>r)	return n;
	int lgs=lg[r-l+1];
	return min(st[l][lgs],st[r-(1<<lgs)+1][lgs]);
}
int query_l(int p,int q)
{
	int l=1,r=p,ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query_st(mid+1,p)>=q)	ans=mid,r=mid-1;
		else	l=mid+1;
	}
	return ans;
}
int query_r(int p,int q)
{
	int l=p,r=n,ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query_st(p+1,mid)>=q)	ans=mid,l=mid+1;
		else	r=mid-1;
	}
	return ans;
}
void modify(int l,int r,int x,int now,int val)
{
	if(l==r)
	{
		maxn[now]=val;
		return ;
	}
	Mm;
	if(x<=mid)	modify(l,mid,x,lc(now),val);
	else	modify(mid+1,r,x,rc(now),val);
	maxn[now]=max(maxn[lc(now)],maxn[rc(now)]);
}
int query(int l,int r,int x,int y,int now)
{
	if(l>=x && r<=y)	return maxn[now];
	Mm;
	int ans=-10086001;
	if(x<=mid)	ans=max(ans,query(l,mid,x,y,lc(now)));
	if(mid+1<=y)	ans=max(ans,query(mid+1,r,x,y,rc(now)));
	return ans;
}
bool check(int x,int y)
{
	int l=query_l(x,y),r=query_r(x,y);
//	if(query(1,n,l,r,1)>=y)
	return query(1,n,l,r,1)<y;
}
int main(){
	scanf("%d",&n);
	if(n==1)	return puts("1")&0;
	scanf("%s",a+1);
	MakeSuffixArray();
	MakeHeightArray();
	for(int i=1;i<=n;++i)	st[i][0]=height[i];
	for(int j=1;j<=20;++j)	for(int i=1;i+(1<<j)-1<=n;++i)	st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	lg[1]=0;
	for(int i=2;i<=n;++i)	lg[i]=lg[i>>1]+1;
	dp[n]=1;
	int pointer=n,ans=0;
	for(int i=n-1;i;--i)
	{
		while(check(Rank[i],pointer-i) && check(Rank[i+1],pointer-i) && i<pointer)	--pointer,modify(1,n,Rank[pointer+1],1,dp[pointer+1]);
		ans=max(ans,dp[i]=pointer-i+1);
	}
	printf("%d",ans);
	return 0;
}