发现这样起标题更能引流(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;
}
标签:产奶,BZOJ1717,后缀,height,int,MAXN,数组,sa
From: https://www.cnblogs.com/XiaoQuQu/p/17975645