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

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

题意

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

解法

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

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

于是我们想到了后缀数组的 \(height\) 数组,以下用 \(h_i\) 代表 \(height_i\),即 \(sa_i,sa_{i-1}\) 的最长公共前缀,如果你不知道什么是 \(height\) 数组,你可能需要用别的解法或者先去学习一下后缀数组。

考虑经典定理:\(lcp(sa_i,sa_j)=\min\limits_{p=l+1}^j h_p\),且对于任意一个区间 \([l,r]\) 来说,若有 \(\forall i \in [l+1,r],h_i\ge x\),那么显然 \(sa_l,sa_{l+1},\cdots,sa_r\) 的最长公共前缀的长度都不小于 \(x\)。于是,问题就被我们转换为,在 \(h\) 数组上是否存在连续的 \(k-1\) 个不小于 \(x\) 的数,显然二分即可解决。

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

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 @ 2024-01-19 21:15  小蛐蛐awa  阅读(362)  评论(0)    收藏  举报