Problem
Solution
先建出后缀自动机,然后我们可以按照right集合来dp。不妨设f[x]表示为x节点表示的子串所能得到的最优答案。
那么有状态转移方程 ,当然了,v要满足在u中出现了至少两次。那么可以在parent树上做这样的一个事情,用top表示从节点到根的路径上最优的节点,即最近的一个转移的。为了判断,我们用线段树合并乱搞出一个right集合。当然由于是从parent树上自上而下继承的,那么至少会在pos[u]处出现过一次,那么我们就只需要查询是否出现第二次。则此时的这一个有效区间为 ,如果有值则说明出现了第二次。
注意一下pos的转移,hhh我调这个调了40min
Code
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=400010,maxm=10000010;
int n,tot=1,lst=1,sz,ans=1,p[maxn],a[maxn],l[maxn],pre[maxn],ch[maxn][26];
int pos[maxn],top[maxn],f[maxn],rt[maxn],lc[maxm],rc[maxm];
char s[maxn>>1];
void update(int l,int r,int pos,int &rt)
{
if(!rt) rt=++sz;
if(l==r) return ;
int m=(l+r)>>1;
if(pos<=m) update(l,m,pos,lc[rt]);
else update(m+1,r,pos,rc[rt]);
}
int query(int l,int r,int L,int R,int rt)
{
if(!rt) return 0;
if(L<=l&&r<=R) return 1;
int m=(l+r)>>1;
if(L<=m&&query(l,m,L,R,lc[rt])) return 1;
if(m<R&&query(m+1,r,L,R,rc[rt])) return 1;
return 0;
}
inline int merge(int x,int y)
{
if(!x||!y) return x|y;
int nw=++sz;
lc[nw]=merge(lc[x],lc[y]);
rc[nw]=merge(rc[x],rc[y]);
return nw;
}
void insert(int c,int id)
{
int p=lst,np=++tot;
lst=np;l[np]=l[p]+1;pos[np]=id;
for(;p&&!ch[p][c];p=pre[p]) ch[p][c]=np;
if(!p) pre[np]=1;
else
{
int q=ch[p][c];
if(l[p]+1==l[q]) pre[np]=q;
else
{
int nq=++tot;l[nq]=l[p]+1;pos[nq]=pos[q];
memmove(ch[nq],ch[q],sizeof(ch[q]));
pre[nq]=pre[q];pre[q]=pre[np]=nq;
for(;ch[p][c]==q;p=pre[p]) ch[p][c]=nq;
}
}
update(1,n,id,rt[lst]);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++) insert(s[i]-'a',i);
for(int i=1;i<=tot;i++) a[l[i]]++;
for(int i=1;i<=n;i++) a[i]+=a[i-1];
for(int i=tot;i;i--) p[a[l[i]]--]=i;
for(int i=tot;i>1;i--) rt[pre[p[i]]]=merge(rt[pre[p[i]]],rt[p[i]]);
for(int i=2;i<=tot;i++)
{
int x=p[i],fa=pre[x];
if(fa==1){f[x]=1;top[x]=x;continue;}
if(query(1,n,pos[x]-l[x]+l[top[fa]],pos[x]-1,rt[top[fa]]))
f[x]=f[fa]+1,top[x]=x;
else f[x]=f[fa],top[x]=top[fa];
if(ans<f[x]) ans=f[x];
}
printf("%d\n",ans);
return 0;
}