Codeforces 700E Cool Slogans
Codeforces 700E Cool Slogans
题目大意:给出一个字符串 \(S\),让你构造一个序列 \(S_1,S_2\dots S_k\),满足 \(\forall S_i\) \(S_i\) 是 \(S\) 的字串,且对于 \(i > 1\) ,\(S_i\) 在 \(S_{i-1}\) 中出现了至少两次,最大化 \(k\) 的长度。\(|S|\leq 2\times 10^5\)。
解题思路:
可以证明一个很牛逼的结论,对于 \(\text{parents}\) 树上的一个节点 \(u\) ,其在其子树中的任意节点 \(v\) 代表的串 \(maxv\) ,节点 \(u\) 代表的所有串在 \(maxv\) 中的所有串出现次数相同。
假设有两个串出现次数不同,那么一定存在一个出现位置使得 \(u\) 代表的一个串 \(k\) 是 \(maxv\) 的前缀但是 \(maxu\) 不是,这样会发现 \(maxu\) 和 \(k\) 的 \(\text{right}\) 集合不同,矛盾。
那么有了这个结论每个节点随便选个长度代表它都是一样的,显然每个串选 \(maxu\) 对于其祖先来说最优,然后就可以在 \(parent\) 拿节点做状态了。
贪心地从后往前做,相当于在 \(parent\) 树从根往下做能放就放,正确性显然。令 \(f[u]\) 表示当前考虑到 \(u\) 节点的最长答案,那么只需要判断 \(u\) 节点的 \(\max\) 能否放在上一个选的串前面即可。由于 \(right\) 集合是一个等价类,那么对于每个串的所有出现位置只需要取最前面一个即可,然后用线段树合并维护 \(right\) 集合判断上一个串是否出现两次即可。由于上一个串是 \(u\) 的一个祖先,还需要记一下上一个选的串对应节点是什么,复杂度 \(O(|S|logn)\)。
code
/*program by mangoyang*/
#include <bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 400005;
char s[N]; int n, ans;
namespace Seg{
#define mid ((l + r) >> 1)
int lc[N*25], rc[N*25], sz[N*25], size;
inline void ins(int &u, int l, int r, int pos){
if(!u) u = ++size;
if(l == r) return (void) (sz[u]++);
if(pos <= mid) ins(lc[u], l, mid, pos);
else ins(rc[u], mid + 1, r, pos);
sz[u] = sz[lc[u]] + sz[rc[u]];
}
inline int merge(int x, int y, int l, int r){
if(!x || !y) return x + y;
int o = ++size;
if(l == r) sz[o] = sz[x] + sz[y];
else{
lc[o] = merge(lc[x], lc[y], l, mid);
rc[o] = merge(rc[x], rc[y], mid + 1, r);
sz[o] = sz[lc[o]] + sz[rc[o]];
}
return o;
}
inline int query(int u, int l, int r, int L, int R){
if(l >= L && r <= R) return sz[u];
int res = 0;
if(L <= mid) res += query(lc[u], l, mid, L, R);
if(mid < R) res += query(rc[u], mid + 1, r, L, R);
return res;
}
#undef mid
}
namespace SAM{
vector<int> g[N];
int ch[N][26], id[N], len[N], fa[N], dp[N], rt[N], pos[N], tail = 1, size = 1;
inline int newnode(int x){ return len[++size] = x, size; }
inline void ins(int c, int x){
int p = tail, np = newnode(len[p] + 1); pos[np] = x;
Seg::ins(rt[np], 1, n, pos[np]);
for(; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
if(!p) return (void) (fa[np] = 1, tail = np);
int q = ch[p][c];
if(len[q] == len[p] + 1) fa[np] = q;
else{
int nq = newnode(len[p] + 1); pos[nq] = x;
fa[nq] = fa[q], fa[q] = fa[np] = nq;
for(int i = 0; i < 26; i++) ch[nq][i] = ch[q][i];
for(; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}tail = np;
}
inline void addedge(){
for(int i = 2; i <= size; i++) g[fa[i]].push_back(i);
}
inline void dfs(int u){
for(int i = 0; i < (int) g[u].size(); i++)
dfs(g[u][i]), rt[u] = Seg::merge(rt[u], rt[g[u][i]], 1, n);
}
inline void dfs2(int u){
if(u > 1){
if(fa[u] == 1) id[u] = u, dp[u] = 1;
else{
int tmp = Seg::query(rt[id[fa[u]]], 1, n, pos[u] - len[u] + len[id[fa[u]]], pos[u] - 1);
if(tmp) dp[u] = dp[fa[u]] + 1, id[u] = u; else dp[u] = dp[fa[u]], id[u] = id[fa[u]];
}
}
ans = max(ans, dp[u]);
for(int i = 0; i < (int) g[u].size(); i++) dfs2(g[u][i]);
}
}
int main(){
read(n), scanf("%s", s + 1);
for(int i = 1; i <= n; i++)
SAM::ins(s[i] - 'a', i);
SAM::addedge(), SAM::dfs(1), SAM::dfs2(1);
cout << ans << endl;
return 0;
}