【YBT2022寒假Day6 C】【luogu CF1063F】子串选取 / String Journey(SAM)(线段树)(倍增)

子串选取 / String Journey

题目链接:YBT2022寒假Day6 C / luogu CF1063F

题目大意

给你一个字符串,要你找最大的 k 满足你可以从左往右一次选 k 个不交子串,满足前一个是后一个的真子串。

思路

首先我们考虑小小贪心一下,每个子串肯定是之前的长度减一,那么长度就是: x , x − 1 , x − 2 , . . . , 2 , 1 x,x-1,x-2,...,2,1 x,x1,x2,...,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+1fi+1
简单证一下,移项得到 f i + 1 − 1 ⩽ f i f_{i+1}-1\leqslant f_{i} fi+11fi 我们把从 i + 1 i+1 i+1 位置的全部减去第一个字符就可以构造一个 i i i 位置的,然后把只有一个的删了所以减一。

然后你可以考虑这样,每次我们假设 f i = f i − 1 + 1 f_i=f_{i-1}+1 fi=fi1+1,然后不断的判断是否可行,这样显然复杂度只会判断大约 n n n 次。

那就到判断的部分,我们要找是否存在 j j j 使得 j ∼ j + f i − 2 j\sim j+f_i-2 jj+fi2 i ∼ i + f i − 1 i\sim i+f_i-1 ii+fi1 的字串。
然后因为长度相差只有 1 1 1,故只有两种可能:是前缀或者后缀。

那我们考虑用 SAM 找到重合的那个部分,也就是我们要重合度达到 f i − 1 f_i-1 fi1
那还有一个问题就是不交,需要 j < i − f i + 1 j<i-f_i+1 j<ifi+1

然后你可以考虑用指针扫过去,为什么呢,因为 i − f i + 1 i-f_i+1 ifi+1 是递增的。
每次 f i f_i fi 至多加一 j j j 也加一,所以不会减少,就是递增的,所以我们就可以用指针维护。

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

然后就可以了。

代码

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值