Description
Solution
Lemma 1
若钦定 s i ( i < k ) s_i(i<k) si(i<k) 为 s i + 1 s_{i+1} si+1 的后缀,答案不变。
Prove 1
假设 s i s_i si 不是 s i + 1 s_{i+1} si+1 的后缀,而只是 s i + 1 s_{i+1} si+1 中由第 l l l 到第 r r r 个字符形成的子串,那么我们完全可以将 s i + 1 s_{i+1} si+1 中的第 r + 1 , r + 2 , ⋯ r+1,r+2,\cdots r+1,r+2,⋯ 个字符全部删去,那么我们就在保证了 ( s 1 , s 2 , ⋯ , s k ) (s_1,s_2,\cdots,s_k) (s1,s2,⋯,sk) 依然是一组合法的字符串 k k k 元组的前提下,使满足 s i s_i si 不是 s i + 1 s_{i+1} si+1 后缀的 i i i 的数量减小了 1 1 1。
不断操作下去,直到上述值变为 0 0 0,我们就将原 k k k 元组等价地转化为了一个满足所有的 s i ( i < k ) s_i(i<k) si(i<k) 均为 s i + 1 s_{i+1} si+1 后缀的新 k k k 元组。证毕。
Lemma 2
建出字符串 S S S 的后缀自动机,则对于 SAM 上的节点 u u u,若其对应的 S S S 中的若干个子串分别为 t 1 , t 2 , ⋯ , t l t_1,t_2,\cdots,t_l t1,t2,⋯,tl,则其中不存在 p , q ( 1 ≤ p , q ≤ l ) p,q(1 \le p,q \le l) p,q(1≤p,q≤l) 使得 t p t_p tp 在 t q t_q tq 中出现超过 1 1 1 次。
Prove 2
考虑反证,假设对于 ( p 0 , q 0 ) (p_0,q_0) (p0,q0) 满足 t p 0 t_{p_0} tp0 在 t q 0 t_{q_0} tq0 中出现超过 1 1 1 次。
那么不难发现, t p 0 t_{p_0} tp0 的 endpos 集合大小必然大于 t q 0 t_{q_0} tq0 的 endpos 集合大小,这与 SAM 的性质相矛盾。证毕。
根据前两个引理,我们考虑建出 S S S 的后缀自动机,那么 s 1 , s 2 , ⋯ , s k s_1,s_2,\cdots,s_k s1,s2,⋯,sk 所对应的节点就是一条从上至下的链,且 SAM 的每个节点处至多只有一个 s i s_i si。
考虑 dp \text{dp} dp。
令 f u f_u fu 表示,从根到 u u u 所组成的最长合法链的长度。其中,一条链 t 1 → t 2 → ⋯ → t x t_1 \to t_2 \to \cdots \to t_x t1→t2→⋯→tx 合法,当且仅当 ∀ i < x \forall i < x ∀i<x 均满足 t i t_i ti 中被选定的链在 t i + 1 t_{i+1} ti+1 中被选定的链的出现次数超过 1 1 1 次。
注意到,因为 SAM 上的一个节点会对应若干个子串,而上述判断链是否合法要涉及到具体的子串,那么在 dp \text{dp} dp 的过程中,我们不仅得维护走到的节点编号,还要维护选了该节点对应的哪个子串,从而 dp \text{dp} dp 的状态数目会达到 O ( ∣ S ∣ 2 ) O(|S|^2) O(∣S∣2) 级别。需要优化。
Key Observation
神仙结论,也是本题的核心。
引理给出,对于节点 u , v u,v u,v 而言,若 u u u 是 v v v 的祖先且 v v v 对应的最长串为 B B B,那么对于每个 u u u 对应的 S S S 的子串 A A A, A A A 在 B B B 中出现的位置所组成的集合均完全相同。
Key Prove
其实就是他写得太好了并且我懒得复述仅此而已罢了
根据 Key Observation,我们可以得到结论:假设选定了 SAM 中由上至下的节点 t 1 , t 2 , ⋯ , t x t_1,t_2,\cdots,t_x t1,t2,⋯,tx,那么 ∀ i \forall i ∀i,都必定会选定 t i t_i ti 对应的字符串中串长最大的那一个。
这样一来,我们就可以写出 dp \text{dp} dp 转移式了:
f u = max OK ( v , u ) = 1 { f v } + 1 f_u=\max_{\text{OK}(v,u)=1} \{f_v\}+1 fu=OK(v,u)=1max{fv}+1
其中 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 当且仅当 v v v 对应的最长串在 u u u 对应的最长串中至少出现了 2 2 2 次。
直接转移的话,时间复杂度是 O ( m 2 ) O(m^2) O(m2) 的,考虑优化。
首先,不难注意到 f u f_u fu 不小于 f f a u f_{fa_u} ffau,其中 f a u fa_u fau 表示 u u u 在 SAM 上的父亲节点。再根据转移式,很容易发现 f u f_u fu 只能等于 f f a u f_{fa_u} ffau 或 f f a u + 1 f_{fa_u}+1 ffau+1。从而,我们只需要判断是否存在一个满足 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 的 v v v 使得 f v = f f a u f_v=f_{fa_u} fv=ffau 就好了。
不难发现,这些节点都挤在了从 1 1 1 到 f a u fa_u fau 的链上的末端 x → f a u x \to fa_u x→fau。其次,不难注意到满足 OK ( v , u ) = 1 \text{OK}(v,u)=1 OK(v,u)=1 的 v v v 必然是从 1 1 1 到 f a u fa_u fau 的链的顶上若干个点。从而,我们只要判断 x x x 是否满足 OK ( x , u ) = 1 \text{OK}(x,u)=1 OK(x,u)=1 就行了。
于是,问题转化为: 给定两个点 x , u x,u x,u,判断 OK ( x , u ) \text{OK}(x,u) OK(x,u) 是否为 1 1 1。不难发现,这等价于查询一个子树内有多少个前缀点(即建树时维护一段前缀的 SAM 上的点)使得其维护的前缀编号落在某一区间内,更进一步的,这等价于一个强制在线的二维数点问题。使用主席树或预先可持久化线段树合并即可。
综上所述,我们通过挖掘若干个神奇结论,之后在后缀自动机上套用线段树一族的数据结构解决了这道神仙题。时间复杂度线性对数。
Code
挺好写的,但是 WA 了好几次。
#include <bits/stdc++.h>
#pragma GCC optimize("Ofast")
using namespace std;
const int maxl=400005,maxg=33;
int n,res;
int fa[maxl],is_pre[maxl],len[maxl],le[maxl],ri[maxl],dep[maxl],root[maxl];
vector<int> GA[maxl];
namespace PR{
int tot=0;
struct President_tree{int ls,rs,sum;}tree[maxl*maxg];
void pushup(int rt){tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;}
int clone(int rt){tree[++tot]=tree[rt];return tot;}
int build_tree(int l,int r,int rt){
if (!rt) rt=(++tot);
if (l==r) return rt;
int mid=(l+r)>>1;
tree[rt].ls=build_tree(l,mid,tree[rt].ls);
tree[rt].rs=build_tree(mid+1,r,tree[rt].rs);
return rt;
}
int change(int nl,int l,int r,int rt,int k){
rt=clone(rt);
if (l==r) {tree[rt].sum++;return rt;}
int mid=(l+r)>>1;
if (nl<=mid) tree[rt].ls=change(nl,l,mid,tree[rt].ls,k);
else tree[rt].rs=change(nl,mid+1,r,tree[rt].rs,k);
pushup(rt);
return rt;
}
int query(int nl,int nr,int l,int r,int rt){
if (nl<=l&&r<=nr) return tree[rt].sum;
int mid=(l+r)>>1,res=0;
if (nl<=mid) res=query(nl,nr,l,mid,tree[rt].ls);
if (nr>mid) res+=query(nl,nr,mid+1,r,tree[rt].rs);
return res;
}
int Query(int l,int r,int x,int y){return query(x,y,1,n,root[r])-query(x,y,1,n,root[l-1]);}
bool ok_trans(int u,int v){return (u==1||Query(le[u],ri[u],is_pre[v]-len[v]+len[u],is_pre[v])>=2);}
}
//SAM
namespace SAM{
int T=0,las=1,tot=1,t[maxl][26],lis[maxl];
void insert(int c,int id){
int p=las,np=(las=(++tot));len[np]=len[p]+1,is_pre[np]=id;
for (;p&&!t[p][c];p=fa[p]) t[p][c]=np;
if (!p) fa[np]=1;
else{
int q=t[p][c];
if (len[q]==len[p]+1) fa[np]=q;
else{
int nq=(++tot);fa[nq]=fa[q],fa[np]=fa[q]=nq,len[nq]=len[p]+1;
for (int i=0;i<26;i++) t[nq][i]=t[q][i];
for (;p&&t[p][c]==q;p=fa[p]) t[p][c]=nq;
}
}
}
void build_graph(){
for (int i=1;i<=tot;i++) GA[fa[i]].push_back(i);
}
void dfs1(int u){
dep[u]=dep[fa[u]]+1;
if (is_pre[u]) le[u]=ri[u]=(++T),lis[T]=is_pre[u];
else le[u]=n+1,ri[u]=0;
for (auto v:GA[u]){
dfs1(v);
le[u]=min(le[u],le[v]),ri[u]=max(ri[u],ri[v]);
if (is_pre[v]) is_pre[u]=is_pre[v];
}
}
void init_segment_tree(){
root[0]=PR::build_tree(1,n,1);
for (int i=1;i<=n;i++) root[i]=PR::change(lis[i],1,n,root[i-1],1);
}
void work(){build_graph(),dfs1(1),init_segment_tree();}
}
//mainwork
namespace ducati{
int f[maxl],nxt[maxl];
void get_all_in(){
cin>>n;
for (int i=1;i<=n;i++){
char x;cin>>x;
SAM::insert(x-'a',i);
}
SAM::work();
}
void DP(int u,int x){
f[u]=f[fa[u]];
if (PR::ok_trans(x,u)) f[u]++,x=u,res=max(res,f[u]);
for (auto v:GA[u]) DP(v,x);
}
void work(){
for (auto u:GA[1]) nxt[1]=u,DP(u,1);
cout<<res<<endl;
}
void solve(){get_all_in(),work();}
}
signed main(){ducati::solve();return 0;}