区间众数问题
简介
一个数列的众数是指其中出现次数最多的数,求解区间众数问题有很多种方法。
方法1
注意,此种方法只用于求解众数出现次数。
使用莫队求解。
可是如何维护呢?
首先进行离散化。
我们定义 \(cnt_i\) 表示数字 \(i\) 在当前区间中的出现次数,\(tot_i\) 表示当前区间中出现次数为 \(i\) 的数字数量,\(p\) 表示当前区间众数的出现次数。
于是,我们就可以这么在扩大区间时维护(设扩大后的端点为 \(x\)):
\[\begin{array}{l}
tot_{cnt_{a_x}}\leftarrow tot_{cnt_{a_x}}-1\\
cnt_{a_x}\leftarrow cnt_{a_x}+1\\
tot_{cnt_{a_x}}\leftarrow tot_{cnt_{a_x}}+1\\
p=\max (p,cnt_{a_x})
\end{array}
\]
这么维护缩小(设缩小前的端点为 \(x\)):
\[\begin{array}{l}
tot_{cnt_{a_x}}\leftarrow tot_{cnt_{a_x}}-1\\
cnt_{a_x}\leftarrow cnt_{a_x}-1\\
tot_{cnt_{a_x}}\leftarrow tot_{cnt_{a_x}}+1\\
p\leftarrow\begin{cases} tot_{cnt_{a_x}}=0,p-1\\tot_{cnt_{a_x}}\ne0,p\end{cases}
\end{array}
\]
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200001, S = 448;
struct Node {
int l, r, id;
}s[MAXN];
int n, q, a[MAXN], b[MAXN], cnt[MAXN], tot[MAXN], ans[MAXN], top;
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
for(int i = 1; i <= n; ++i) {
if(!tot || b[i] > b[top]) {
b[++top] = b[i];
}
}
for(int i = 1; i <= n; ++i) {
a[i] = lower_bound(b + 1, b + top + 1, a[i]) - b;
}
for(int i = 1; i <= q; ++i) {
cin >> s[i].l >> s[i].r;
s[i].id = i;
}
sort(s + 1, s + q + 1, [](const Node &a, const Node &b) { return (a.l / S == b.l / S ? (a.l / S % 2 ? a.r > b.r : a.r < b.r) : a.l / S < b.l / S); });
cnt[a[1]]++, tot[1]++;
for(int i = 1, x = 1, y = 1, p = 1; i <= q; ++i) {
for(; x > s[i].l; tot[cnt[a[--x]]++]--, tot[cnt[a[x]]]++, p = max(p, cnt[a[x]])) {
}
for(; y < s[i].r; tot[cnt[a[++y]]++]--, tot[cnt[a[y]]]++, p = max(p, cnt[a[y]])) {
}
for(; x < s[i].l; tot[cnt[a[x]]]--, (!tot[p] ? p-- : p = p), tot[--cnt[a[x++]]]++) {
}
for(; y > s[i].r; tot[cnt[a[y]]]--, (!tot[p] ? p-- : p = p), tot[--cnt[a[y--]]]++) {
}
ans[s[i].id] = p;
}
for(int i = 1; i <= q; ++i) {
cout << ans[i] << "\n";
}
return 0;
}
方法2
注意,此类方法只适用于求解绝对众数。
绝对众数是指数列中出现次数 \(>\frac{N}{2}\) 的数,一个数列的绝对众数至多一个。
这里用的方法是一种玄学的随机化。
因为绝对众数出现次数超过一半,所以我们可以随机出数列中的一个数,每次都有 \(\frac{1}{2}\) 的概率抽到绝对众数,所以我们抽 \(k\) 次存在绝对众数但没找到的概率为 \(\frac{1}{2^k}\)。
现在考虑如何判断一个数是否是绝对众数。方法也很简单:首先进行离散化,将相等的数出现位置放入一个 vector 中,再通过二分求出 \(l-r\) 有多少个当前数。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 300005;
int n, q, a[MAXN], b[MAXN], tot;
vector<int> v[MAXN];
mt19937 gen(time(0));
int rnd(int l, int r) {
uniform_int_distribution<int> Rand(l, r);
return Rand(gen);
}
int Calc(int l, int r, int x) {
auto a = lower_bound(v[x].begin(), v[x].end(), l), b = upper_bound(v[x].begin(), v[x].end(), r);
return b - a;
}
int Find(int l, int r) {
int x = rnd(l, r), cnt = Calc(l, r, a[x]);
for(int i = 1; i <= 60 && cnt <= (r - l + 1) / 2; x = rnd(l, r), cnt = Calc(l, r, a[x]), ++i) {
}
return (cnt <= (r - l + 1) / 2 ? -1 : b[a[x]]);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
for(int i = 1; i <= n; ++i) {
if(!tot || b[i] > b[tot]) {
b[++tot] = b[i];
}
}
for(int i = 1; i <= n; ++i) {
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
v[a[i]].push_back(i);
}
for(int i = 1, l, r; i <= q; ++i) {
cin >> l >> r;
cout << Find(l, r) << "\n";
}
return 0;
}

浙公网安备 33010602011771号