\(\text{Solution:}\)
\(dp,\) 思路都是对的 又死细节上了 对 SAM 的理解还是不够……(或者应该说是 \(dp\))
首先考虑一下什么情况我们可以接上一个串。题目给的是出现了两次,那转化到 SAM 上,我们如何用已知信息来判别?
首先证明一个结论,如果 \(t\) 可以接到当前串的后面,那必然存在一种最优方案使 \(s\) 是 \(t\) 的后缀。
若不是,考虑把后面多余的切掉不会劣。
那么就可以直接扔到 parent 树上考虑了。继续考虑在这种情况下我们如何判定。
假定我们已经求出了一个 \(t\) 的 endpos,
直接扔结论:
\[\exist i\in [endpos-len[t]+len[s],endpos-1],i\in endpos_s
\]
证明:首先其作为后缀已经出现了一次,剩下的一次需要在 \(endpos\) 之前,最右端也就是 \(endpos-1;\) 而左端至少要到起点加上其长度的位置。
那就可以愉快 \(dp\) 了,设 \(f[i]\) 表示以 parent 树上 \(i\) 节点结尾的最长长度,显然如果符合条件就直接 \(f[i]=f[x]+1.\) 不符合呢?
当时写的时候就默认给删了……实际上这样不对,显然舍去了一些答案呀……
观察一下,在树上一个后缀容易在深度更深的后缀中出现两次。
这也就意味着我们的直接舍去的做法是错的。所以我们还需要记录一下一个点的前驱状态。
初始的时候转移状态默认为自己,如果一个节点其父亲无法与当前点匹配,那就令其前驱状态为其父亲。
转移的时候均默认从前驱状态转移即可。复杂度是 \(O(n\log n),\) 因为需要线段树合并维护 endpos 来支持查询。
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
const int TN=1e7+10;
inline int Min(int x,int y){return x<y?x:y;}
inline int Max(int x,int y){return x>y?x:y;}
int n;
char s[N];
namespace SGT{
int ls[TN],rs[TN],node;
void change(int &x,const int &L,const int &R,const int &pos){
if(!x)x=++node;
if(L==R)return;
int mid=(L+R)>>1;
if(pos<=mid)change(ls[x],L,mid,pos);
else change(rs[x],mid+1,R,pos);
}
int merge(const int &x,const int &y){
if(!x||!y)return x|y;
int p=++node;
ls[p]=merge(ls[x],ls[y]);
rs[p]=merge(rs[x],rs[y]);
return p;
}
bool query(const int &x,const int &L,const int &R,const int &l,const int &r){
if(!x)return false;
if(L>=l&&R<=r)return true;
int mid=(L+R)>>1;
bool res=false;
if(l<=mid)res|=query(ls[x],L,mid,l,r);
if(mid<r)res|=query(rs[x],mid+1,R,l,r);
return res;
}
}
using namespace SGT;
namespace SAM{
int len[N],pa[N],ch[N][26],last=1,tot=1,col[N],minr[N],f[N],rt[N];
int siz[N],prestate[N];
vector<int>G[N];
queue<int>q;
void insert(const int &c,const int &cl){
int p=last;int np=++tot;last=tot;siz[np]=1;
len[np]=len[p]+1;col[np]=cl;minr[np]=cl;
for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np;
if(!p)pa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1)pa[np]=q;
else{
int nq=++tot;
len[nq]=len[p]+1;
pa[nq]=pa[q];pa[q]=pa[np]=nq;
memcpy(ch[nq],ch[q],sizeof ch[q]);
for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq;
}
}
}
void dfs(int x){
if(col[x])change(rt[x],1,n,col[x]);
for(auto v:G[x]){
dfs(v);
rt[x]=merge(rt[x],rt[v]);
minr[x]=Max(minr[x],minr[v]);
siz[x]+=siz[v];
}
}
void Build(){
for(int i=2;i<=tot;++i)G[pa[i]].push_back(i);
dfs(1);
}
void Dp(){
q.push(1);f[1]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(auto v:G[x]){
int endpos=minr[v];
// printf("%d:[%d %d]\n",v,endpos-len[v]+len[x],endpos-1);
if(query(rt[prestate[x]],1,n,endpos-len[v]+len[prestate[x]],endpos-1))f[v]=Max(f[v],f[prestate[x]]+1);//,cout<<"?\n";
else {
f[v]=Max(f[v],f[x]),prestate[v]=prestate[x];
}
q.push(v);
}
}
}
}
using namespace SAM;
int ep[N];
int main(){
// freopen("in.txt","r",stdin);
scanf("%d",&n);
scanf("%s",s+1);
memset(minr,-0x3f,sizeof minr);
for(int i=1;i<=n;++i){
int v=s[i]-‘a‘;
ep[i]=tot+1;
insert(v,i);
}
for(int i=1;i<=tot;++i)f[i]=1,prestate[i]=i;
Build();
// for(int i=1;i<=n;++i){
// printf("%d %d %d\n",ep[i],siz[ep[i]],minr[ep[i]]);
// }
// for(int i=1;i<=tot;++i)cout<<minr[i]<<" ";
// cout<<endl;
Dp();
// for(int i=1;i<=tot;++i)cout<<f[i]<<" ";
// puts("");
int ans=-1;
for(int i=1;i<=tot;++i)ans=Max(ans,f[i]);
printf("%d\n",ans);
return 0;
}