正题
引理1:当k最大时,存在一组解满足s[i+1]是s[i]的后缀
如果存在最大的k为答案,若有s[i+1]不是s[i]的后缀,那么将s[i]的一部分后缀去掉,使得s[i+1]是s[i]的后缀,显然对k值没有影响.
有了这个引理,我们就可以在fail树上暴力往下跳了,只要对与父亲的每一个len,看看在子节点的right集合中[l+len-1,r-1]中是否有点即可.
引理2:父亲节点所管理的每一个串,在子节点所管理串中的出现次数相等.
让我们来举个例子证明:
若a="bcd",b="abcd",a,b属于一个等价类x
若s="bcdeabcd",s属于等价类p,且s为p中的最长串,x为p的父亲.
此时a在s中出现了一次,b在s中出现了两次.由于a,b的right集合相等,那么必然存在一个串w="abcdeabcd",由于s是p中最长串,所以w所属的等价类y必然是p的孩子,那么|right(p)|>|right(y)|,存在h属于right(p),而不属于right(y),那么也就是说有一个位置有s串没有w串,同时说明了一个地方有a串而没有b串,这与a,b属于同一个等价类x矛盾.
我们找祖先的最长就好.
#include<bits/stdc++.h>
using namespace std;
const int N=400010;
int rt[N],ls[25000010],rs[25000010],T,f[N],g[N];
void insert(int&now,int x,int l,int r){
if(now==0) now=++T;
if(l==r) return ;
int mid=(l+r)/2;
if(x<=mid) insert(ls[now],x,l,mid);
else insert(rs[now],x,mid+1,r);
}
int merge(int x,int y){
if(!x || !y) return x|y;
int now=++T;
ls[now]=merge(ls[x],ls[y]);
rs[now]=merge(rs[x],rs[y]);
return now;
}
bool gs(int now,int x,int y,int l,int r){
if(now==0) return false;
if(x==l && y==r) return true;
int mid=(l+r)/2;
if(y<=mid) return gs(ls[now],x,y,l,mid);
else if(mid<x) return gs(rs[now],x,y,mid+1,r);
else return gs(ls[now],x,mid,l,mid)|gs(rs[now],mid+1,y,mid+1,r);
}
struct Saffix_AutoMaton{
int n,tot,rig[N],fail[N],len[N],ch[N][26],las,sum[N],a[N];
char s[N];
void init(int x){memset(ch[x],0,sizeof(ch[x]));fail[x]=rig[x]=len[x]=0;}
void pre(){init(0);tot=las=0;fail[0]=-1;}
void extend(int pos,int c){
int x=++tot,p=las;
len[x]=len[p]+1;las=x;rig[x]=pos;insert(rt[x],pos,1,n);
while(p!=-1 && !ch[p][c]) ch[p][c]=x,p=fail[p];
if(p==-1) fail[x]=0;
else if(len[ch[p][c]]==len[p]+1) fail[x]=ch[p][c];
else{
int q=++tot,tmp=ch[p][c];init(q);
len[q]=len[p]+1;fail[q]=fail[tmp];fail[x]=fail[tmp]=q;
for(int i=0;i<26;i++) ch[q][i]=ch[tmp][i];
while(p!=-1 && ch[p][c]==tmp) ch[p][c]=q,p=fail[p];
}
}
void build(){
scanf("%d",&n);
scanf("%s",s+1);pre();
//n=strlen(s+1);
for(int i=1;i<=n;i++) extend(i,s[i]-'a'),sum[i]=0;
for(int i=1;i<=tot;i++) sum[len[i]]++;
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
for(int i=1;i<=tot;i++) a[sum[len[i]]--]=i;
for(int i=tot;i>=1;i--){
rig[fail[a[i]]]=max(rig[fail[a[i]]],rig[a[i]]);
rt[fail[a[i]]]=merge(rt[fail[a[i]]],rt[a[i]]);
}
}
void solve(){
int ans=1;
for(int i=1;i<=tot;i++){
if(fail[a[i]]==1){f[a[i]]=1,g[a[i]]=a[i];}
if(gs(rt[g[fail[a[i]]]],rig[a[i]]-len[a[i]]+len[g[fail[a[i]]]],rig[a[i]]-1,1,n))
f[a[i]]=f[fail[a[i]]]+1,g[a[i]]=a[i];
else f[a[i]]=f[fail[a[i]]],g[a[i]]=g[fail[a[i]]];
ans=max(ans,f[a[i]]);
}
printf("%d\n",ans);
}
}SAM;
int main(){
SAM.build();SAM.solve();
}
本文探讨了基于Sufffix Automaton(SA)优化的最长串匹配问题,提出两个关键引理,通过fail树遍历与子节点匹配检查,确保串在子节点管理串中的等频出现,实现高效字符串匹配。

被折叠的 条评论
为什么被折叠?



