P8496 [NOI2022] 众数
补题狗不发表评论,但是摩尔投票是不是有点那啥
小心 deque
摩尔投票
对于一个数列,如果其众数出现次数严格大于数列长度的一半,就可以用摩尔投票的方式 \(O(n)\) 求出众数。
当众数出现次数小于一半时,求出众数之后还需要检验。
模板:P2397 yyy loves Maths VI (mode)
摩尔投票具有可加性,因此可以用线段树维护区间摩尔投票。例题:P3765 总统选举
思路
线段树合并 + 摩尔投票。
首先注意到题目给出众数出现次数严格大于一半的条件,考虑用摩尔投票求众数。
正常想法是对每个序列维护一棵普通线段树,但是这样的话插入需要记录长度之类的很麻烦,而且检验众数的时候不能快速求一个值在某个数列中的出现次数,所以考虑换成值域线段树。
对于 1 2 操作,需要动态维护每个序列两端的数,也就是需要动态维护每个数列。注意到不同的数列之间只有合并操作,考虑启发式合并维护 4 操作。
因为要访问两端所以用 deque,结果发现 1e6 个压缩 deque 遇到 CCF 评测机变答辩糕,直接 MLE 送走一车人。遂怒写 1e6 个链表模拟 deque.
这样对于 1 2 操作只需要简单维护一下,4 用线段树合并 + 启发式合并链表可以做,接下来考虑 3 操作。
显然拼接完给的区间之后,大区间的众数只有可能在给出区间的众数中取。反之每个区间中都是非众数的数出现次数均小于一半,加起来显然也不可能是大区间的众数。
于是我们只需要把给出的每个区间中的众数拉出来,再做一次摩尔投票就行了。检验直接用值域线段树算出现次数。
时间复杂度 \(O(n \log n)\).
代码
#include <cstdio>
#include <utility>
using namespace std;
#define fi first
#define se second
typedef long long ll;
const int maxn = 1e6 + 5;
const int sgt_sz = maxn * 30;
int n, q, id;
int se[maxn], rt[maxn];
int sz[maxn], head[maxn], pre[maxn], lst[maxn], val[maxn];
namespace SGT
{
int nd;
int ls[sgt_sz], rs[sgt_sz], val[sgt_sz], cnt[sgt_sz];
void push_up(int rt)
{
if (cnt[ls[rt]] > cnt[rs[rt]]) val[rt] = val[ls[rt]], cnt[rt] = cnt[ls[rt]];
else val[rt] = val[rs[rt]], cnt[rt] = cnt[rs[rt]];
}
void update(int &rt, int l, int r, int p, int w)
{
if (!rt) rt = ++nd;
if (l == r) return val[rt] = l, cnt[rt] += w, void();
int mid = (l + r) >> 1;
if (p <= mid) update(ls[rt], l, mid, p, w);
else update(rs[rt], mid + 1, r, p, w);
push_up(rt);
}
int query(int rt, int l, int r, int p)
{
if (!rt) return 0;
if (l == r) return cnt[rt];
int mid = (l + r) >> 1;
if (p <= mid) return query(ls[rt], l, mid, p);
return query(rs[rt], mid + 1, r, p);
}
void merge(int &rt, int a, int b, int l, int r)
{
if ((!a) || (!b)) return rt = a | b, void();
if (!rt) rt = ++nd;
if (l == r) return val[rt] = l, cnt[rt] = cnt[a] + cnt[b], void();
int mid = (l + r) >> 1;
merge(ls[rt], ls[a], ls[b], l, mid);
merge(rs[rt], rs[a], rs[b], mid + 1, r);
push_up(rt);
}
}
void clear(int x) { head[x] = sz[x] = lst[x] = 0; }
void psh_back(int x, int y)
{
val[++id] = y, sz[x]++;
if (!head[x]) head[x] = id;
pre[id] = lst[x], lst[x] = id;
}
void pp_back(int x)
{
lst[x] = pre[lst[x]], sz[x]--;
if (!sz[x]) head[x] = 0;
}
int main()
{
scanf("%d%d", &n, &q);
int vl = 0, vr = n + q + 1;
for (int i = 1, l; i <= n; i++)
{
scanf("%d", &l);
for (int j = 1, v; j <= l; j++)
{
scanf("%d", &v), v++;
psh_back(i, v);
SGT::update(rt[i], vl, vr, v, 1);
}
}
while (q--)
{
int opt;
scanf("%d", &opt);
if (opt == 1)
{
int x, y;
scanf("%d%d", &x, &y), y++;
psh_back(x, y), SGT::update(rt[x], vl, vr, y, 1);
}
else if (opt == 2)
{
int x;
scanf("%d", &x);
SGT::update(rt[x], vl, vr, val[lst[x]], -1), pp_back(x);
}
else if (opt == 3)
{
int m;
scanf("%d", &m);
for (int i = 1; i <= m; i++) scanf("%d", &se[i]);
pair<int, ll> nw(0, 0); ll cur = 0;
for (int i = 1; i <= m; i++)
{
cur += sz[se[i]];
if (SGT::cnt[rt[se[i]]] <= sz[se[i]] / 2) continue;
ll res = SGT::cnt[rt[se[i]]] * 2ll - sz[se[i]];
if (SGT::val[rt[se[i]]] == nw.fi) nw.se += res;
else if (nw.se == res) nw = {0, 0};
else if (nw.se < res) nw = {SGT::val[rt[se[i]]], res - nw.se};
else nw.se -= res;
}
if (!nw.fi) { puts("-1"); continue; }
ll chk = 0;
for (int i = 1; i <= m; i++) chk += SGT::query(rt[se[i]], vl, vr, nw.fi);
if (chk > cur / 2) printf("%d\n", nw.fi - 1);
else puts("-1");
}
else
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
SGT::merge(rt[c], rt[a], rt[b], vl, vr);
lst[c] = (lst[b] ? lst[b] : lst[a]), head[c] = (head[a] ? head[a] : head[b]);
if (head[b]) pre[head[b]] = lst[a];
sz[c] = sz[a] + sz[b];
clear(a), clear(b);
}
}
return 0;
}