「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\)
\[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\)
看似麻煩了,因為現在我們要轉移 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}\)
#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;
}