【YBT2022寒假Day6 C】【luogu CF1063F】子串选取 / String Journey(SAM)(线段树)(倍增)
子串选取 / String Journey
题目链接:YBT2022寒假Day6 C / luogu CF1063F
题目大意
给你一个字符串,要你找最大的 k 满足你可以从左往右一次选 k 个不交子串,满足前一个是后一个的真子串。
思路
首先我们考虑小小贪心一下,每个子串肯定是之前的长度减一,那么长度就是:\(x,x-1,x-2,...,2,1\)
那我一开始都不确定不好搞,考虑翻转,然后就是 \(1,2,...,x\)。
然后这里有个性质,就是 \(f_{i+1}\leqslant f_{i}+1\)。
简单证一下,移项得到 \(f_{i+1}-1\leqslant f_{i}\) 我们把从 \(i+1\) 位置的全部减去第一个字符就可以构造一个 \(i\) 位置的,然后把只有一个的删了所以减一。
然后你可以考虑这样,每次我们假设 \(f_i=f_{i-1}+1\),然后不断的判断是否可行,这样显然复杂度只会判断大约 \(n\) 次。
那就到判断的部分,我们要找是否存在 \(j\) 使得 \(j\sim j+f_i-2\) 是 \(i\sim i+f_i-1\) 的字串。
然后因为长度相差只有 \(1\),故只有两种可能:是前缀或者后缀。
那我们考虑用 SAM 找到重合的那个部分,也就是我们要重合度达到 \(f_i-1\)。
那还有一个问题就是不交,需要 \(j<i-f_i+1\)。
然后你可以考虑用指针扫过去,为什么呢,因为 \(i-f_i+1\) 是递增的。
每次 \(f_i\) 至多加一 \(j\) 也加一,所以不会减少,就是递增的,所以我们就可以用指针维护。
然后至于用哪个转移我们可以用线段树。
因为你找有没有你要保证它是子串,所以我们在 SAM 弄出的 parent 树上找对应点的儿子。
那这个儿子里面的信息用线段树,找到这个点在 SAM 上的位置你可以用倍增跳到 \(len=f_i-1\) 的位置。
然后就可以了。
代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int n, f[500001], pl[500001];
char s[500001];
struct node {
int son[31], fa, len;
}d[1000005];
int lst, tot;
vector <int> e[1000005];
void SAM_insert(int x) {
int p = lst, np = ++tot; lst = np;
d[np].len = d[p].len + 1;
for (; p && !d[p].son[x]; p = d[p].fa) d[p].son[x] = np;
if (!p) d[np].fa = 1;
else {
int q = d[p].son[x];
if (d[q].len == d[p].len + 1) d[np].fa = q;
else {
int nq = ++tot;
d[nq] = d[q];
d[nq].len = d[p].len + 1;
d[np].fa = d[q].fa = nq;
for (; p && d[p].son[x] == q; p = d[p].fa) d[p].son[x] = nq;
}
}
}
int fa[1000005][21], dfnn;
int dfn[1000005], ed[1000005];
void dfs(int now) {
dfn[now] = ++dfnn;
for (int i = 1; i <= 20; i++)
fa[now][i] = fa[fa[now][i - 1]][i - 1];
for (int i = 0; i < e[now].size(); i++) {
int x = e[now][i];
fa[x][0] = now; dfs(x);
}
ed[now] = dfnn;
}
struct XD_tree {
int val[1000005 << 2];
void up(int now) {
val[now] = max(val[now << 1], val[now << 1 | 1]);
}
void insert(int now, int l, int r, int pl, int va) {
if (l == r) {
val[now] = va; return ;
}
int mid = (l + r) >> 1;
if (pl <= mid) insert(now << 1, l, mid, pl, va);
else insert(now << 1 | 1, mid + 1, r, pl, va);
up(now);
}
int query(int now, int l, int r, int L, int R) {
if (L <= l && r <= R) return val[now];
int mid = (l + r) >> 1, re = 0;
if (L <= mid) re = query(now << 1, l, mid, L, R);
if (mid < R) re = max(re, query(now << 1 | 1, mid + 1, r, L, R));
return re;
}
}T;
int jump(int now, int k) {
if (!now) return 0;
for (int i = 20; i >= 0; i--)
if (d[fa[now][i]].len >= k)
now = fa[now][i];
return now;
}
bool check(int i) {
int p1 = jump(pl[i], f[i] - 1);
int p2 = jump(pl[i - 1], f[i] - 1);
return (max(p1 ? T.query(1, 1, dfnn, dfn[p1], ed[p1]) : -1, p2 ? T.query(1, 1, dfnn, dfn[p2], ed[p2]) : -1) >= (f[i] - 1));
}
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
reverse(s + 1, s + n + 1);
lst = tot = 1; d[0].len = -1;
for (int i = 1; i <= n; i++) {
SAM_insert(s[i] - 'a');
pl[i] = lst;
}
for (int i = 2; i <= tot; i++)
e[d[i].fa].push_back(i);
dfs(1);
int ans = 0, R = 0;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + 1;
while (!check(i)) {
f[i]--; R++;
T.insert(1, 1, dfnn, dfn[pl[R]], f[R]);
}
ans = max(ans, f[i]);
}
printf("%d", ans);
return 0;
}