子串选取 / String Journey
题目链接:YBT2022寒假Day6 C / luogu CF1063F
题目大意
给你一个字符串,要你找最大的 k 满足你可以从左往右一次选 k 个不交子串,满足前一个是后一个的真子串。
思路
首先我们考虑小小贪心一下,每个子串肯定是之前的长度减一,那么长度就是: x , x − 1 , x − 2 , . . . , 2 , 1 x,x-1,x-2,...,2,1 x,x−1,x−2,...,2,1
那我一开始都不确定不好搞,考虑翻转,然后就是 1 , 2 , . . . , x 1,2,...,x 1,2,...,x。
然后这里有个性质,就是
f
i
+
1
⩽
f
i
+
1
f_{i+1}\leqslant f_{i}+1
fi+1⩽fi+1。
简单证一下,移项得到
f
i
+
1
−
1
⩽
f
i
f_{i+1}-1\leqslant f_{i}
fi+1−1⩽fi 我们把从
i
+
1
i+1
i+1 位置的全部减去第一个字符就可以构造一个
i
i
i 位置的,然后把只有一个的删了所以减一。
然后你可以考虑这样,每次我们假设 f i = f i − 1 + 1 f_i=f_{i-1}+1 fi=fi−1+1,然后不断的判断是否可行,这样显然复杂度只会判断大约 n n n 次。
那就到判断的部分,我们要找是否存在
j
j
j 使得
j
∼
j
+
f
i
−
2
j\sim j+f_i-2
j∼j+fi−2 是
i
∼
i
+
f
i
−
1
i\sim i+f_i-1
i∼i+fi−1 的字串。
然后因为长度相差只有
1
1
1,故只有两种可能:是前缀或者后缀。
那我们考虑用 SAM 找到重合的那个部分,也就是我们要重合度达到
f
i
−
1
f_i-1
fi−1。
那还有一个问题就是不交,需要
j
<
i
−
f
i
+
1
j<i-f_i+1
j<i−fi+1。
然后你可以考虑用指针扫过去,为什么呢,因为
i
−
f
i
+
1
i-f_i+1
i−fi+1 是递增的。
每次
f
i
f_i
fi 至多加一
j
j
j 也加一,所以不会减少,就是递增的,所以我们就可以用指针维护。
然后至于用哪个转移我们可以用线段树。
因为你找有没有你要保证它是子串,所以我们在 SAM 弄出的 parent 树上找对应点的儿子。
那这个儿子里面的信息用线段树,找到这个点在 SAM 上的位置你可以用倍增跳到
l
e
n
=
f
i
−
1
len=f_i-1
len=fi−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;
}