CF700E Cool Slogans
题意:给定一个字符串 \(S\),要求构造字符串序列 \(s_1,s_2,\ldots,s_k\),满足每个 \(s_i\) 都是 \(S\) 的子串,且 \(\forall i\in[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_u\gets 1(\)初值\()\)
- \(dp_u\gets dp_v+1(v\) 在 \(u\) 的子树内且 \(u\) 所接收的串在 \(v\) 所接收的串中出现了两次\()\)
其中第二个转移看起来比较难处理。
我们考虑一个 \(v\) 对它的哪些祖先有贡献,发现只要 \(v\) 对祖先 \(u\) 有贡献,它对 \(u\) 的所有祖先也都有贡献了。
这是一个单调性,所以我们可以用一个倍增来找到最近的 \(u\)。
那如何判断出现了两次呢?
考虑 \(v\) Right 集合中随便一个位置 \(p\),找到 \(u\) Right 集合中小于 \(p\) 的最大位置 \(q\),那么出现了两次当且仅当 \(q-len_u\ge p-len_v\)。
相当于问 \(u\) 的子树内,小于 \(p\) 的 \(q\) 当中,最大的有没有达到 \(p-len_v+len_u\)。DFS 序 + 可持久化线段树即可(当然也可以 可持久化可合并线段树 这样子)。
找到了最近的 \(u\),那剩下的祖先没转移到啊?加一个新转移 \(dp_u\gets dp_v(v\) 是 \(u\) 的儿子\()\) 就可以了。
倍增一个 log,线段树查询一个 log,总复杂度 \(O(n\log^2 n)\)。
有一件我没有想到的事情是,用树上双指针之类的东西来替代倍增,可以 \(O(n\log 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\n",dp[1]);
return 0;
}
浙公网安备 33010602011771号