题目
给你一个长度为
n
n
的字符串,求最长的一个字符串序列
a[1]...a[k]
a
[
1
]
.
.
.
a
[
k
]
满足序列中的每一个字符串都是
S
S
的子串,且对于任意的都有
a[i−1]
a
[
i
−
1
]
在
a[i]
a
[
i
]
中至少出现两次。两次出现允许重叠。求最大的
k
k
。
题解
有人想到用SA做,每次将字符串 a[i] a [ i ] 的最高的height对应的字符串成为 a[i+1] a [ i + 1 ] 。但是如果最高的height值不止一个咋办?这下GG了。
审题
题目条件:①
a[i−1]
a
[
i
−
1
]
在
a[i]
a
[
i
]
中至少出现两次。
②两次出现允许重叠。
③使得
k
k
最大。
解题思路
Easy档:子串可以视为的后缀的前缀。考虑利用”
a[i−1]
a
[
i
−
1
]
在
a[i]
a
[
i
]
中至少出现两次”这个条件。则相当于固定了最前面的字母后,KMP中成功地匹配了该后缀的前面几个字符?但是不要忽略条件”两次出现允许重叠”。
依据以上的分析,设
f[i,j]
f
[
i
,
j
]
表示
s[i..j]
s
[
i
.
.
j
]
的最多的border次数。这显然对应着最大的
k
k
。
则。
Hard档:不要放弃之前想到的一切。这都是有用的。
什么东西与后缀有关?并且能够通过这个算法求出最多的border次数。
可以考虑SAM的性质。如果有一次border,那么说明了某个点的fail链上的祖先y的right集中的某个元素处于某个区间。
设
f[x]
f
[
x
]
表示x点对应的字符串的最大的border次数。
①
y
y
的right集包含了的right集。这说明x点对应的字符串出现在哪,那里必然有
y
y
对应的字符串出现。
②设,若
p′∈right[y]且p′∈[p−mxLen[x]+mnLen[y],p−1]
p
′
∈
r
i
g
h
t
[
y
]
且
p
′
∈
[
p
−
m
x
L
e
n
[
x
]
+
m
n
L
e
n
[
y
]
,
p
−
1
]
,则说明
x
x
可以通过转移过来。
③要求的是最大值,所以只需记录fail链上
f
f
值最大的那个即可。
SAM维护right集
根据SAM的构造,每次的点的right集里只有一个元素,为对应的
|S|
|
S
|
。
根据
fail[x]
f
a
i
l
[
x
]
的right集包含了
x
x
的right集,可以通过主席树合并和
x
x
的right集。
每次需要开新的点,表示点合并后的新right集。
int merge(int px,int py){
if(!px||!py)return px+py;
int nx=++tot;
tr[nx].sum=tr[px].sum+tr[py].sum;
tr[nx].ls=merge(tr[px].ls,tr[py].ls);
tr[nx].rs=merge(tr[px].rs,tr[py].rs);
return nx;
}
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 400010
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
int ls,rs,sum;
};note tr[N*20];
struct note_ed{
int to,next;
};note_ed edge[N];
int Tot,head[N],head1[N],tot;
int i,j,k,l,n,m,wz,gs,ans;
char ch,s[N];
int st[N],P[N],pos[N];
int sam[N][26],fail[N],Len[N];
int dep[N],fa[N],f[N];
int rt[N];
bool bz[N];
void update(int ps){
tr[ps].sum=tr[tr[ps].ls].sum+tr[tr[ps].rs].sum;
}
void change(int &px,int py,int l,int r,int x){
if(!px)px=++tot;
tr[px]=tr[py];
tr[px].sum++;
if(l==r)return;
int wz=(l+r)>>1;
if(x<=wz)change(tr[px].ls,tr[py].ls,l,wz,x);
else change(tr[px].rs,tr[py].rs,wz+1,r,x);
update(px);
}
int merge(int px,int py){
if(!px||!py)return px+py;
int nx=++tot;
tr[nx].sum=tr[px].sum+tr[py].sum;
tr[nx].ls=merge(tr[px].ls,tr[py].ls);
tr[nx].rs=merge(tr[px].rs,tr[py].rs);
return nx;
}
int query(int ps,int l,int r,int x,int y){
if(!ps)return 0;
if(l==x&&r==y)return tr[ps].sum;
int wz=(l+r)>>1;
if(y<=wz)return query(tr[ps].ls,l,wz,x,y);else
if(x>wz)return query(tr[ps].rs,wz+1,r,x,y);else
return query(tr[ps].ls,l,wz,x,wz)+query(tr[ps].rs,wz+1,r,wz+1,y);
}
void lb(int x,int y){
edge[++Tot].to=y;
edge[Tot].next=head[x];
head[x]=Tot;
}
int ins(int x,int wz){
int j,np=++gs,nq,p=wz,q;
Len[np]=Len[p]+1;
while(p&&!sam[p][x])sam[p][x]=np,p=fail[p];
if(!p)fail[np]=1;else{
q=sam[p][x];
if(Len[q]==Len[p]+1)fail[np]=q;else{
nq=++gs;
fo(j,0,25)sam[nq][j]=sam[q][j];
fail[nq]=fail[q];
fail[np]=fail[q]=nq;
Len[nq]=Len[p]+1;
while(p&&sam[p][x]==q)sam[p][x]=nq,p=fail[p];
}
}
change(rt[np],rt[1],1,n,i);
P[np]=i;
return np;
}
void dg(int x){
int i,T,y;bool p;
st[T=1]=x;
memcpy(head1,head,sizeof(head1));
while(T){
p=0;
x=st[T];
for(i=head1[x];i;i=edge[i].next){
head1[x]=edge[i].next;
st[++T]=edge[i].to;
p=1;
break;
}
if(!p){
if(T>1){
x=st[T-1];y=st[T];
rt[x]=merge(rt[x],rt[y]);
P[x]=P[x]>P[y]?P[x]:P[y];
}
T--;
}
}
}
void dg1(int x){
int i,T,y;bool p;
st[T=1]=x;
memcpy(head1,head,sizeof(head1));
memset(bz,0,sizeof(bz));
while(T){
p=0;
x=st[T];
if(!bz[x]){
bz[x]=1;
if(x!=1){
pos[x]=pos[fail[x]];
y=P[x]-Len[x]+Len[fail[pos[x]]]+1;
if(query(rt[pos[x]],1,n,y,P[x]-1)){
f[x]=f[pos[x]]+1;
pos[x]=x;
ans=ans>f[x]?ans:f[x];
}
}
}
for(i=head1[x];i;i=edge[i].next){
head1[x]=edge[i].next;
st[++T]=edge[i].to;
p=1;
break;
}
if(!p)T--;
}
}
int main(){
freopen("cat.in","r",stdin);
freopen("cat.out","w",stdout);
ch=getchar();
while(ch>='a'&&ch<='z')s[++n]=ch,ch=getchar();
wz=gs=1;
rt[1]=1;tot=1;
fo(i,1,n)
wz=ins(s[i]-'a',wz);
fo(i,2,gs)lb(fail[i],i);
dg(1);
pos[1]=1;Len[0]=-1;
dg1(1);
printf("%d",ans);
return 0;
}