【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\) 是递增的。
每次 \(f_i\) 至多加一 \(j\) 也加一,所以不会减少,就是递增的,所以我们就可以用指针维护。

然后至于用哪个转移我们可以用线段树。
因为你找有没有你要保证它是子串,所以我们在 SAM 弄出的 parent 树上找对应点的儿子。
那这个儿子里面的信息用线段树,找到这个点在 SAM 上的位置你可以用倍增跳到 \(len=f_i-1\) 的位置。

然后就可以了。

代码

#include
#include
#include

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  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;
}

相关