首先随便就能看出来那个样例是来骗人的。我们要答案最优,显然 \(|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}\) 的定义,应该满足:
这个过程可以使用 hash
完成。此时我们已经得到了一个很搞笑的算法了。
哦不可是这个毒瘤出题人要求我们用更高效的算法去做才能够对得起这个 \(3200\) 的评分。分析一下性质,通过打表我们也能得到:
画图可以理解:\(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;
}