【CF700E】Cool Slogans
题目
题目链接:https://codeforces.com/problemset/problem/700/E
给定一个字符串 \(S\),要求构造字符串序列 \(s_1,s_2,\ldots,s_k\),满足任意 \(s_i\) 都是 \(S\) 的子串,且任意 \(i\in[2,n]\),都有 \(s_{i-1}\) 在 \(s_i\) 中出现了至少 \(2\) 次(可以有重叠部分,只要起始、结尾位置不同即可)。
求可能的最大的 \(k\) 的值(即序列的最大可能长度)。
\(n\leq 2\times 10^5\)。
思路
首先有一个比较显然的结论,最终序列任意 \(s_i,s{i+1}\) 都应该满足 \(s_i\) 是 \(s_{i+1}\) 的后缀。因为如果不是,那么删掉 \(s_{i+1}\) 后面若干个字符直到 \(s_i\) 是其后缀显然不劣。
那么对于 parent 树上的两个节点 \(x,y\),假设 \(x\) 是 \(y\) 的祖先,那么如果 \(x\) 所表示的等价类中长度最长的串在 \(y\) 长度最长的串中出现了至少两次,那么就可以从 \(x\) 转移到 \(y\)。
由于 \(x\) 中任意一个字符串必然是 \(y\) 的后缀,所以对于 \(y\) 的 \(\mathrm{endpos}\) 集合中任意一个元素 \(\mathrm{pos}_y\),如果存在一个 \(\mathrm{pos}_x\) 满足 \(\mathrm{pos}_x\in[\mathrm{pos}_y-\mathrm{len}_y+\mathrm{len}_x,\mathrm{pos}_y)\),那么 \(x\) 就至少在 \(y\) 的任意串中出现了至少两次。
所以我们先把 SAM 建好,用可持久化线段树合并求出每一个节点的 \(\mathrm{endpos}\),然后在 parent 树上 dp 即可。
注意任意节点不一定只能从其父亲转移而来,所以需要记录一个 \(g_x\) 表示节点 \(x\) 是被哪一个节点转移到的。
时间复杂度 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=400010,LG=20;
int n,tot,ans,head[N],rt[N],a[N],b[N],f[N],g[N];
char s[N];
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
struct SegTree
{
int tot,lc[N*LG*4],rc[N*LG*4];
bool flag[N*LG*4];
int ins(int l,int r,int k)
{
int x=++tot;
flag[x]=1;
if (l==r) return x;
int mid=(l+r)>>1;
if (k<=mid) lc[x]=ins(l,mid,k);
if (k>mid) rc[x]=ins(mid+1,r,k);
return x;
}
int merge(int x,int y)
{
if (!x || !y) return x|y;
int p=++tot;
flag[p]=flag[y]|flag[x];
lc[p]=merge(lc[x],lc[y]);
rc[p]=merge(rc[x],rc[y]);
return p;
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql<=l && qr>=r) return flag[x];
int mid=(l+r)>>1,s=0;
if (ql<=mid) s|=query(lc[x],l,mid,ql,qr);
if (qr>mid) s|=query(rc[x],mid+1,r,ql,qr);
return s;
}
}seg;
struct SAM
{
int tot,last,len[N],pos[N],fa[N],ch[N][26];
SAM() { tot=last=1; }
void ins(int c,int id)
{
int p=last,np=++tot;
last=np; len[np]=len[p]+1; pos[np]=id;
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=1;
else
{
int q=ch[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot;
len[nq]=len[p]+1; fa[nq]=fa[q]; pos[nq]=pos[q];
for (int i=0;i<26;i++) ch[nq][i]=ch[q][i];
fa[np]=fa[q]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void buildseg()
{
for (int i=2;i<=tot;i++)
rt[i]=seg.ins(1,n,pos[i]);
for (int i=1;i<=tot;i++) b[len[i]]++;
for (int i=1;i<=tot;i++) b[i]+=b[i-1];
for (int i=1;i<=tot;i++) a[b[len[i]]--]=i;
for (int i=tot;i>=1;i--)
{
int j=a[i];
if (fa[j]!=1)
rt[fa[j]]=seg.merge(rt[fa[j]],rt[j]);
add(fa[j],j);
}
}
void dfs(int x)
{
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (x!=1 && seg.query(rt[g[x]],1,n,pos[v]-len[v]+len[g[x]],pos[v]-1))
f[v]=f[g[x]]+1,g[v]=v;
else if (x!=1)
f[v]=f[g[x]],g[v]=g[x];
else
f[v]=1,g[v]=v;
ans=max(ans,f[v]);
dfs(v);
}
}
}sam;
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%s",&n,s+1);
for (int i=1;i<=n;i++)
sam.ins(s[i]-'a',i);
sam.buildseg();
sam.dfs(1);
printf("%d",ans);
return 0;
}