题目
题目链接:https://codeforces.com/problemset/problem/1063/F
对于一个字符串数组 (t_1),....,(t_k),若对于每一个 (t_i) 都是 (t_i-1) 的真子串的话,则称为有序串组,列如 ({ab,b}) 是,({ab,c}) 和 ({a,a}) 不是。
给定字符串 (s),构造有序串组 (t_1,...,t_k) 和任意字符串数组 (u_1,...,u_{k+1}),使 (s=u_1+t_1+u_2+t_2...+t_k+u_{k+1}),其中 (+) 为字符串的拼接。
现在给定一个字符串,求满足条件的最大 (k)。
(nleq 5 imes 10^5)。
思路
设 (f_i) 表示以 (i) 开头的后缀选出有序串组的最大数量(必须选 (i))。
首先有几个相对明显的性质:
-
对于一个有序串组 (t_1,t_2,cdots t_k),任意相邻的两项的长度一定差 (1)。
显然吧,不然删掉多余的字符不会更劣。 -
对于 (s) 一个以 (i) 开头的后缀,如果可以选出长度为 (k) 的有序串组,那么一定可以选出长度为 (k-1) 的有序串组。
因为我们把这 (k) 字符串的最后一个字符都删去后显然得到了一个长度为 (k-1) 的有序串组。 -
(f_{i+1}geq f_{i}-1)。
我们可以把从 (i) 开始的最长有序串组中每一个字符串的第一个字符删去,就可以得到一个从 (i+1) 开始,长度为 (f_{i}-1) 的有序串组。
由性质 3,我们可以得到 (f_ileq f_{i+1}+1)。
这也就意味着 (sum^{n-1}_{i=1}|f_i-f_{i+1}|) 是 (O(n)) 级别的。假设我们一定确定了 (f_{i+1}sim f_{n}),我们可以通过从 (f_{i+1}+1) 倒序枚举到 (1),并判断 (f_i) 是否可以为当前枚举的这一个值。根据性质 2,(f_i) 越大肯定越优。
那么现在就转化成了一个判定性问题了。
假设枚举到 (res),考虑如何判断 (f_i) 能否为 (res)。根据性质 1,如果 (f_i) 可以为 (res),那么必然存在一个 (j),满足:
- (jgeq i+f_i)。
- (f_jgeq f_i-1)
- (s[i:i+f_i-2]) 是字符串 (s) 中以 (j) 开头的后缀的前缀,或 (s[i+1:i+f_i-1]) 是字符串 (s) 中以 (j) 开头的后缀的前缀。
看到一个后缀的前缀,我们不难想到建出反串的 SAM,那么在原串中,如果 (s[l:r]) 是一个后缀 (s[k:n]) 的前缀,在反串 SAM 上 (s[l:r]) 所在等价类一定是 (s[k:n]) 所在等价类在 parent 树上的祖先。
给 parent 树的点按照 dfs 序编号后(记 (pos_i) 表示以 (i) 开头的后缀所在等价类在 parent 树上的 dfs 序,(id[i]) 表示 SAM 上点 (i) 的 dfs 序),我们的操作转化为:
- 在 (pos_j) 插入一个点 ((j,f_j))。
- 询问 (left [id[k],id[k]+siz[k] ight )) 的区间内是否存在二元组 ((a,b)) 满足 (ageq i+f_i),且 (bgeq f_{i}-1)。
这是一个二维偏序。但是我们发现随着 (i) 的减小,(i+f_i) 是单调不增的,所以可以直接指针维护一下第一维,第二维用线段树即可。
时间复杂度 (O(nlog n))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000010,LG=20;
int n,m,ans,tot,f[N],pos[N],head[N],siz[N],id[N],pa[N][LG+1];
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 SAM
{
int last,tot,ch[N][26],fa[N],len[N];
SAM() { last=tot=1; len[0]=-1; }
void ins(int c,int j)
{
int p=last,np=++tot;
len[np]=len[p]+1; last=pos[j]=np;
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;
fa[nq]=fa[q]; len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[nq]));
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
}sam;
struct SegTree
{
int maxn[N*4];
void update(int x,int l,int r,int k,int v)
{
maxn[x]=max(maxn[x],v);
if (l==r) return;
int mid=(l+r)>>1;
if (k<=mid) update(x*2,l,mid,k,v);
else update(x*2+1,mid+1,r,k,v);
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql<=l && qr>=r) return maxn[x];
int mid=(l+r)>>1,res=0;
if (ql<=mid) res=max(res,query(x*2,l,mid,ql,qr));
if (qr>mid) res=max(res,query(x*2+1,mid+1,r,ql,qr));
return res;
}
}seg;
void build()
{
m=sam.tot;
memset(head,-1,sizeof(head));
for (int i=2;i<=m;i++) add(sam.fa[i],i);
}
void dfs(int x)
{
pa[x][0]=sam.fa[x]; id[x]=++tot; siz[x]=1;
for (int i=1;i<=LG;i++)
pa[x][i]=pa[pa[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
dfs(v); siz[x]+=siz[v];
}
}
int find(int x,int k)
{
for (int i=LG;i>=0;i--)
if (sam.len[pa[x][i]]>=k) x=pa[x][i];
return x;
}
bool check(int i)
{
int x=find(pos[i],f[i]-1),y=find(pos[i+1],f[i]-1);
if (seg.query(1,1,m,id[x],id[x]+siz[x]-1)>=f[i]-1) return 1;
if (seg.query(1,1,m,id[y],id[y]+siz[y]-1)>=f[i]-1) return 1;
return 0;
}
int main()
{
scanf("%d%s",&n,s+1);
for (int i=n;i>=1;i--)
sam.ins(s[i]-'a',i);
build(); tot=0; dfs(1);
for (int i=n;i>=1;i--)
{
f[i]=f[i+1]+1;
for (;!check(i);f[i]--)
seg.update(1,1,m,id[pos[i+f[i]-1]],f[i+f[i]-1]);
ans=max(ans,f[i]);
}
cout<<ans;
return 0;
}