BZOJ1717 Milk Patterns 产奶的模式 (二分+后缀数组+height数组)

发现这样起标题更能引流(ylg 实锤了)

题意

给定一个长度为 n 的数组 a,求在 a 中出现了至少 k 次的最长子串的长度。

解法

考虑将一个子串拆成两个后缀,即 [l,r]=[l,n][r,n],发现一个长度为 x 的子串 ti,j 两个位置出现过当且仅当后缀 i,j 有共同前缀 x

于是我们想到二分一个长度 x,接下来的问题是,怎么判断是否有 k 个后缀,他们的公共前缀长度至少为 x

于是我们想到了后缀数组的 height 数组,以下用 hi 代表 heighti,即 sai,sai1 的最长公共前缀,如果你不知道什么是 height 数组,你可能需要用别的解法或者先去学习一下后缀数组。

考虑经典定理:lcp(sai,saj)=minp=l+1jhp,且对于任意一个区间 [l,r] 来说,若有 i[l+1,r],hix,那么显然 sal,sal+1,,sar 的最长公共前缀的长度都不小于 x。于是,问题就被我们转换为,在 h 数组上是否存在连续的 k1 个不小于 x 的数,显然二分即可解决。

这里使用了复杂度为 O(nlog2n)std::sort 求解后缀数组,复杂度即求解后缀数组的 O(nlog2n)

const int MAXN = 2e4 + 5;
int n, k, a[MAXN], rk[MAXN], oldrk[MAXN], sa[MAXN], h[MAXN];

bool check(int x) {
	int cnt = 0;
	for (int i = 1; i <= n; ++i) {
		if (h[i] < x) cnt = 0;
		else cnt++;
		if (cnt == k - 1) return true;
	}
	return false;
}

void work() {
	cin >> n >> k;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) rk[i] = a[i], sa[i] = i;
	for (int _ = 0; (1ll << _) <= n; ++_) {
		const int w = 1ll << _;
		sort(sa + 1, sa + 1 + n, [&](const auto x, const auto y) {
			return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];
		});
		for (int i = 1; i <= n; ++i) oldrk[i] = rk[i];
		for (int p = 0, i = 1; i <= n; ++i) {
			if (oldrk[sa[i]] == oldrk[sa[i - 1]] && 
				oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) rk[sa[i]] = p;
			else rk[sa[i]] = ++p;
		}
	}
	for (int i = 1, p = 0; i <= n; ++i) {
		if (!rk[i]) continue;
		if (p) p--;
		while (a[i + p] == a[sa[rk[i] - 1] + p]) p++;
		h[rk[i]] = p;
	}
	int l = 0, r = n + 1;
	while (l + 1 < r) {
		if (check(mid)) l = mid;
		else r = mid;
	}
	cout << l << endl;
}
posted @   小蛐蛐awa  阅读(330)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示