题意:给定一个字符串 (S),要求构造字符串序列 (s_1,s_2,ldots,s_k),满足每个 (s_i) 都是 (S) 的子串,且 (forall iin[2,k]),(s_i) 在 (s_{i-1}) 中出现了至少 (2) 次,使得 (k) 最大。
字符串题也挺有意思的。
结论 0:必定存在一种最优方案,使字符串序列中 (s_i) 是 (s_{i-1}) 的后缀。
证明显然。
考虑一个 naive 的 DP:(dp_{l,r}) 表示以 (S[l:r]) 为最短字符串的最长序列。根据那个结论,转移时只要找到所有 (S[l,r]=S[l',r'](l'<l)),然后 (dp_{l,r}leftarrow dp_{l',r}+1) 即可。
这个复杂度有点大,那我们把 SAM 建出来看看。
那我们设 (dp_u),表示以结点 (u) 所接收的子串为最短字符串的最长序列,那么 (dp_u) 很明显可以从 Parent 树里 (u) 的子树中的某些 (dp_v) 转移来。具体地:
- (dp_ugets 1()初值())
- (dp_ugets dp_v+1(v) 在 (u) 的子树内且 (u) 所接收的串在 (v) 所接收的串中出现了两次())
其中第二个转移看起来比较难处理。
我们考虑一个 (v) 对它的哪些祖先有贡献,发现只要 (v) 对祖先 (u) 有贡献,它对 (u) 的所有祖先也都有贡献了。
这是一个单调性,所以我们可以用一个倍增来找到最近的 (u)。
那如何判断出现了两次呢?
考虑 (v) Right 集合中随便一个位置 (p),找到 (u) Right 集合中小于 (p) 的最大位置 (q),那么出现了两次当且仅当 (q-len_uge p-len_v)。
相当于问 (u) 的子树内,小于 (p) 的 (q) 当中,最大的有没有达到 (p-len_v+len_u)。DFS 序 + 可持久化线段树即可(当然也可以 可持久化可合并线段树 这样子)。
找到了最近的 (u),那剩下的祖先没转移到啊?加一个新转移 (dp_ugets dp_v(v) 是 (u) 的儿子()) 就可以了。
倍增一个 log,线段树查询一个 log,总复杂度 (O(nlog^2 n))。
有一件我没有想到的事情是,用树上双指针之类的东西来替代倍增,可以 (O(nlog n))。
可是我们刚刚的做法其实包含着两个结论没有证明。
结论 1:若存在 (u) 所接收的串 (s_u) 在 (v) 所接收的串 (s_v) 中出现了两次,那么,对于 (u) 所接收的任何串 (s'_u),存在一个 (v) 所接收的串 (s'_v),(s'_u) 在 (s'_v) 中出现了两次。
结论 2:同一个结点 (u) 所接收的所有子串 答案相等。
这两个结论证明应该不难,就不写了。
#include<cstdio>
#include<algorithm>
const int N=2e5+3,K=20;
struct edge{int v,nxt;}g[N+N];
int n,fa[N+N][K],son[N+N][26],len[N+N],pos[N+N],lst,t,head[N+N],k,dfn[N],ord[N],dfn2[N+N],siz[N+N],dfc,dp[N+N];
char a[N];
inline void Insert(int u,int v){g[++k]=(edge){v,head[u]};head[u]=k;}
void Dfs0(int u){
int v;
for(int j=1;j<K;j++)fa[u][j]=fa[fa[u][j-1]][j-1];
dfn2[u]=dfc+1;
if(pos[u]>0)
ord[dfn[pos[u]]=++dfc]=pos[u],siz[u]=1;
for(int i=head[u];i;i=g[i].nxt){
v=g[i].v;
Dfs0(v);
siz[u]+=siz[v];
}
}
#define M (L+R>>1)
struct segment_tree{
int mx[N*K],ls[N*K],rs[N*K],t,rt[N];
int Max(int l,int r,int L,int R,int k){
if(!k||l>r)return 0;
if(l<=L&&R<=r)return mx[k];
if(r<=M)return Max(l,r,L,M,ls[k]);
if(l> M)return Max(l,r,M+1,R,rs[k]);
return std::max(Max(l,r,L,M,ls[k]),Max(l,r,M+1,R,rs[k]));
}
void Update(int p,int a,int L,int R,int k0,int&k1){
k1=++t,ls[k1]=ls[k0],rs[k1]=rs[k0];
mx[k1]=std::max(mx[k0],a);
if(L==R)return;
p<=M?Update(p,a,L,M,ls[k0],ls[k1]):Update(p,a,M+1,R,rs[k0],rs[k1]);
}
}tr;
void Dfs1(int u){
int v,w;
for(int i=head[u];i;i=g[i].nxt){
v=g[i].v,Dfs1(v);
dp[u]=std::max(dp[u],dp[v]);
pos[u]=pos[v];
}
v=u;
for(int j=K-1;~j;j--)
if((w=fa[v][j])&&tr.Max(dfn2[w],dfn2[w]+siz[w]-1,1,n,tr.rt[pos[u]-1])-len[w]<pos[u]-len[u])
v=w;
if(fa[v][0]>1)dp[fa[v][0]]=std::max(dp[fa[v][0]],dp[u]+1);
}
int main(){
int u,v,w,p,c;
scanf("%d%s",&n,a+1);
lst=t=1;
for(int i=1;i<=n;i++){
c=a[i]-'a';
u=++t,len[u]=i,pos[u]=i;
for(v=lst;v&&!son[v][c];v=fa[v][0])son[v][c]=u;
if(v){
p=son[v][c];
if(len[p]==len[v]+1)fa[u][0]=p;
else{
w=++t,len[w]=len[v]+1;
fa[w][0]=fa[p][0],fa[u][0]=fa[p][0]=w;
for(int j=0;j<26;j++)son[w][j]=son[p][j];
for(;v&&son[v][c]==p;v=fa[v][0])son[v][c]=w;
}
}else fa[u][0]=1;
lst=u;
}
for(u=2;u<=t;u++)Insert(fa[u][0],u);
Dfs0(1);
for(int i=1;i<=n;i++)tr.Update(dfn[i],i,1,n,tr.rt[i-1],tr.rt[i]);
for(u=1;u<=t;u++)dp[u]=1;
Dfs1(1);
printf("%d
",dp[1]);
return 0;
}