区间众数问题

简介

一个数列的众数是指其中出现次数最多的数,求解区间众数问题有很多种方法。

方法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;
}

posted @ 2024-04-25 17:04  Yaosicheng124  阅读(329)  评论(0)    收藏  举报