题解 [CF700E] Cool Slogans
只会到 \(O(n^2)\) /kk
发现最优解链上第一个串长度为 1 一定可行(好像没啥用
发现最优情况下链上上一个串一定是下一个串的最长 boader
于是跑 n 次 kmp 预处理出每个子串的最长 boader 即可
然后正解:
boader 这个限制过强了(虽然用 SA 貌似也可以顺着这个思路往下走
对于本题,把限制弱化成“在一个串中出现了至少两次的后缀”并不会影响答案
那么建出后缀树,每个节点的 endpos 集合相同,所以只考虑等价类中的最长串是可行的
一个祖先节点可以向当前节点转移的条件是祖先节点代表的串在当前节点代表的串中出现了至少 2 次
那么合法的转移点是根到当前点路径上的一个前缀,可以倍增找到
考虑怎么 check 一个转移点是否合法
可以线段树合并维护出 endpos 集合,考虑实际意义,若祖先节点代表的串在当前节点代表的串中出现了至少 2 次
则若当前节点 endpos 集合中包含一个 \(p\),则祖先节点代表的串一定包含 \(p\) 且在 \([p-len_i+len_{anc}, p-1]\) 中还有一个取值
那么任找一个 \(p\) check 即可
这样是 \(O(n\log^2)\) 的,能不能优化呢?
发现 dp 值单调不降且合法转移点是一个前缀且 dp 值每次最多增加 1
所以对每段 dp 值相同的转移点维护深度最浅的点,每次看能不能从父节点的转移点向下一个连续段即可
复杂度 \(O(n\log n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 400010
#define ll long long
//#define int long long
int n;
char s[N];
#define ls(p) lson[p]
#define rs(p) rson[p]
int head[N], f[N], ecnt;
int lson[N*100], rson[N*100], rot[N], cnt;
int len[N], fail[N], tr[N][26], sta[N], up[N], now, tot, top, ans;
struct edge{int to, next;}e[N];
inline void add(int s, int t) {e[++ecnt]={t, head[s]}; head[s]=ecnt;}
void init() {fail[now=tot=0]=-1;}
void upd(int& p, int tl, int tr, int pos) {
if (!p) p=++cnt;
if (tl==tr) return ;
int mid=(tl+tr)>>1;
if (pos<=mid) upd(ls(p), tl, mid, pos);
else upd(rs(p), mid+1, tr, pos);
}
bool query(int p, int tl, int tr, int l, int r) {
if (!p) return 0;
if (l<=tl&&r>=tr) return 1;
int mid=(tl+tr)>>1;
if (l<=mid&&r>mid) return query(ls(p), tl, mid, l, r)|query(rs(p), mid+1, tr, l, r);
else if (l<=mid) return query(ls(p), tl, mid, l, r);
else return query(rs(p), mid+1, tr, l, r);
}
int merge(int p1, int p2, int tl, int tr) {
if (!(p1&&p2)) return p1|p2;
int mid=(tl+tr)>>1, t=++cnt;
ls(t)=merge(ls(p1), ls(p2), tl, mid);
rs(t)=merge(rs(p1), rs(p2), mid+1, tr);
return t;
}
int findfir(int p, int tl, int tr) {
if (tl==tr) return tl;
int mid=(tl+tr)>>1;
if (ls(p)) return findfir(ls(p), tl, mid);
else return findfir(rs(p), mid+1, tr);
}
void show(int p, int tl, int tr) {
if (!p) return ;
if (tl==tr) {cout<<tl<<' '; return ;}
int mid=(tl+tr)>>1;
show(ls(p), tl, mid); show(rs(p), mid+1, tr);
}
void insert(char c) {
c-='a';
int cur=++tot;
len[cur]=len[now]+1;
int p, q;
for (p=now; ~p&&!tr[p][c]; tr[p][c]=cur,p=fail[p]);
if (p==-1) fail[cur]=0;
else if (len[q=tr[p][c]]==len[p]+1) fail[cur]=q;
else {
int cln=++tot;
len[cln]=len[p]+1;
fail[cln]=fail[q];
for (int i=0; i<26; ++i) tr[cln][i]=tr[q][i];
for (; ~p&&tr[p][c]==q; tr[p][c]=cln,p=fail[p]);
fail[cur]=fail[q]=cln;
}
sta[++top]=now=cur;
}
void dfs1(int u) {
for (int i=head[u],v; ~i; i=e[i].next) {
v = e[i].to;
dfs1(v);
rot[u]=merge(rot[u], rot[v], 0, tot);
}
}
// void dfs2(int u, int now) {
// cout<<"dfs2: "<<u<<' '<<now<<endl;
// sta[++top]=u;
// ++now;
// int pos=findfir(rot[u], 0, tot);
// // cout<<"pos: "<<pos<<endl;
// // cout<<"len: "<<len[u]<<endl;
// if (!query(rot[sta[now]], 0, tot, pos-len[u]+len[sta[now]], pos-1)) --now;
// cout<<"now: "<<now<<' '<<pos-len[u]+len[sta[now]]<<' '<<pos-1<<endl;
// ans=max(ans, f[u]=f[sta[now]]+1);
// for (int i=head[u],v; ~i; i=e[i].next) dfs2(e[i].to, now);
// --top;
// }
void dfs2(int u, int fa) {
int pos=findfir(rot[u], 0, tot);
if (query(rot[up[fa]], 0, tot, pos-len[u]+len[up[fa]], pos-1)) f[u]=f[fa]+1, up[u]=u;
else f[u]=f[fa], up[u]=up[fa];
ans=max(ans, f[u]);
for (int i=head[u],v; ~i; i=e[i].next) dfs2(e[i].to, u);
}
signed main()
{
scanf("%d%s", &n, s+1);
init();
memset(head, -1, sizeof(head));
for (int i=1; i<=n; ++i) insert(s[i]);
upd(rot[0], 0, tot, 0);
// cout<<"sta: "; for (int i=1; i<=top; ++i) cout<<sta[i]<<' '; cout<<endl;
for (int i=1; i<=top; ++i) upd(rot[sta[i]], 0, tot, i);
for (int i=1; i<=tot; ++i) add(fail[i], i); //, cout<<"add: "<<fail[i]<<' '<<i<<endl;
dfs1(0);
sta[top=1]=0;
for (int i=head[0],v; ~i; i=e[i].next) dfs2(e[i].to, 0);
cout<<ans<<endl;
// cout<<"i: "; for (int i=1; i<=tot; ++i) cout<<i<<' '; cout<<endl;
// cout<<"f: "; for (int i=1; i<=tot; ++i) cout<<f[i]<<' '; cout<<endl;
// cout<<"endpos: "; show(rot[9], 0, tot); cout<<endl;
// cout<<len[2]<<' '<<len[6]<<' '<<len[10]<<endl;
return 0;
}