P8496 [NOI2022] 众数

1 P8496 [NOI2022] 众数

2 题目描述

对于一个序列,定义其众数为序列中出现次数严格大于一半的数字。注意该定义与一般的定义有出入,在本题中请以题面中给出的定义为准。

一开始给定 n 个长度不一的正整数序列,编号为 1n,初始序列可以为空。这 n 个序列被视为存在,其他编号对应的序列视为不存在。

q 次操作,操作有以下类型:

  • 1 x y:在 x 号序列末尾插入数字 y。保证 x 号序列存在,且 1x,yn+q
  • 2 x:删除 x 号序列末尾的数字,保证 x 号序列存在、非空,且 1xn+q
  • 3 m x1 x2 xm:将 x1,x2,,xm 号序列顺次拼接,得到一个新序列,并询问其众数。如果不存在满足上述条件的数,则返回 1。数据保证对于任意 1imxi 是一个仍然存在的序列,1xin+q,且拼接得到的序列非空。注意:不保证 x1,,xm 互不相同,询问中的合并操作不会对后续操作产生影响。
  • 4 x1 x2 x3:新建一个编号为 x3 的序列,其为 x1 号序列后顺次添加 x2 号序列中数字得到的结果,然后删除 x1,x2 对应的序列。此时序列 x3 视为存在,而序列 x1,x2 被视为不存在,在后续操作中也不会被再次使用。保证 1x1,x2,x3n+qx1x2、序列 x1,x2 在操作前存在、且在操作前没有序列使用过编号 x3

输入格式

输入的第一行包含两个正整数 nq,分别表示数列的个数和操作的次数,保证 n5×105q5×105

接下来 n 行,第 i 行表示编号为 i 的数列。每一行的第一个非负整数 li 表示初始第 i 号序列的数字个数,接下来有 li 个非负整数 ai,j 按顺序表示数列中的数字。假定 Cl=li 代表输入序列长度之和,则保证 Cl5×105ai,jn+q

接下来 q 行,每行若干个正整数,表示一个操作,并按照题面描述中的格式输入。

假定 Cm=m 代表所有操作 3 需要拼接的序列个数之和,则保证 Cm5×105

输出格式

对于每次询问,一行输出一个整数表示对应的答案。

样例 #1

样例输入 #1

Copy
2 8 3 1 1 2 3 3 3 3 3 1 1 3 1 2 4 2 1 3 3 1 3 2 3 3 1 3 1 3 1 3 1 3

样例输出 #1

Copy
1 3 -1 3 -1

样例 #2

样例输入 #2

Copy
4 9 1 1 1 2 1 3 1 4 3 4 1 2 3 4 1 1 2 3 2 1 2 2 3 3 3 1 2 3 1 4 4 1 4 4 1 4 4 3 4 1 2 3 4

样例输出 #2

Copy
-1 2 2 4

提示

【样例解释 #1】

第一次询问查询序列 1 的众数。由于序列包含两个 1,超过序列长度的一半,因此众数为 1

第二次询问查询序列 2 的众数。由于序列只包含 3,因此众数为 3

第三次询问询问序列 3 的众数。此时序列 3(3,3,3,1,1,2),不存在出现次数大于 3 次的数,因此输出为 1


【样例解释 #2】

第一次询问查询序列 1,2,3,4 拼接后得到的序列的众数。拼接的结果为 (1,2,3,4),不存在出现次数大于两次的数,因此输出为 1

第四次询问查询序列 1,2,3,4 拼接后得到的序列的众数。拼接的结果为 (1,2,2,4,4,4,4),众数为 4

【数据范围】

对于所有测试数据,保证 1n,q,Cm,Cl5×105

3 题解

首先不考虑 2 操作。容易发现,此时序列内数的相对顺序并不重要,可以看作若干集合。

考虑 1,4 操作,我们用线段树合并维护这 n+q 个集合即可,由于合并时 x1x2,故复杂度有保障。

如何求解一个集合的绝对众数呢?

容易想到在线段树上每个权值处维护出现次数,然后直接全局最大值即可求出众数,判断是否大于总大小一半即可。

这个做法显然不能扩展到多个集合一起求众数的情况。

考虑性质:如果在线段树上某一区间所有值的出现次数之和小于等于 n2,那么这个区间的所有值的出现次数最大值也一定小于等于 n2

因此,我们维护线段树上数值出现次数的和,查询时在 m 棵线段树上一起二分:

如果 m 棵线段树的左区间的和大于 n2,那么右区间的和一定小于等于 n2,故直接向左区间走。

否则就判断 m 棵线段树的右区间的和是否大于 n2,如果是则向右区间走,否则直接返回 1

考虑 2 操作,对每个序列维护一个链表,1 操作是在末端插入,2 操作是在末端删除,4 操作是将 x1 的末端与 x2 的前端连接。

复杂度 O(mlogV)

4 代码:

Copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #include <cstdlib> #include <cmath> #include <deque> using namespace std; #define pii pair<int,int> #define mp make_pair const int N = 1e6 + 10; #define int long long typedef long long ll; int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int n, q, cnt, tot; int a[N], rt[N]; ll siz[N]; int head[N << 1], nxt[N << 1], tail[N << 1], h[N << 1], pre[N << 1]; struct sgt { int tot; struct node { int ls, rs; ll sum; }t[N * 37]; int build() { tot++; t[tot].ls = t[tot].rs = t[tot].sum = 0; return tot; } void pushup(int p) {t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;} void modify(int p, int l, int r, int pos, ll d) { if (l == r) { t[p].sum += d; return ; } int mid = (l + r) >> 1; if (pos <= mid) { if (!t[p].ls) t[p].ls = build(); modify(t[p].ls, l, mid, pos, d); } else { if (!t[p].rs) t[p].rs = build(); modify(t[p].rs, mid + 1, r, pos, d); } pushup(p); } int merge(int p, int q, int l, int r) { if (!p || !q) return p + q; if (l == r) { t[p].sum += t[q].sum; return p; } int mid = (l + r) >> 1; t[p].ls = merge(t[p].ls, t[q].ls, l, mid); t[p].rs = merge(t[p].rs, t[q].rs, mid + 1, r); pushup(p); return p; } ll S() {ll res = 0; for (int i = 1; i <= cnt; i++) res += t[t[a[i]].ls].sum; return res;} ll S2() {ll res = 0; for (int i = 1; i <= cnt; i++) res += t[t[a[i]].rs].sum; return res;} int query(int l, int r, int d) { if (l == r) return l; int mid = (l + r) >> 1; if (S() > d / 2) { for (int i = 1; i <= cnt; i++) a[i] = t[a[i]].ls; return query(l, mid, d); } if (S2() > d / 2) { for (int i = 1; i <= cnt; i++) a[i] = t[a[i]].rs; return query(mid + 1, r, d); } return -1; } }T; signed main() { n = read(), q = read(); for (int i = 1; i <= n; i++) { siz[i] = read(); rt[i] = T.build(); for (int j = 1; j <= siz[i]; j++) { int x = read(); if (!head[i]) head[i] = ++tot, tail[i] = tot, h[tot] = x; else { nxt[tail[i]] = ++tot; pre[tot] = tail[i]; tail[i] = tot; h[tot] = x; } T.modify(rt[i], 0, n + q, x, 1); } } for (int i = 1; i <= q; i++) { int opt = read(); if (opt == 1) { int x = read(), y = read(); siz[x]++; if (!head[x]) head[x] = ++tot, tail[x] = tot, h[tot] = y; else { nxt[tail[x]] = ++tot; pre[tot] = tail[x]; tail[x] = tot; h[tot] = y; } T.modify(rt[x], 0, n + q, y, 1); } if (opt == 2) { int x = read(); siz[x]--; T.modify(rt[x], 0, n + q, h[tail[x]], -1); if (!siz[x]) head[x] = tail[x] = 0; else { tail[x] = pre[tail[x]]; nxt[tail[x]] = 0; } } if (opt == 3) { int m = read(); ll sum = 0; cnt = 0; for (int j = 1; j <= m; j++) { int x = read(); a[++cnt] = rt[x]; sum += (ll)siz[x]; } printf("%lld\n", T.query(0, n + q, sum)); } if (opt == 4) { int x = read(), y = read(), z = read(); if (!siz[x]) head[z] = head[y], tail[z] = tail[y]; else if (!siz[y]) head[z] = head[x], tail[z] = tail[x]; else { head[z] = head[x]; tail[z] = tail[y]; nxt[tail[x]] = head[y]; pre[head[y]] = tail[x]; } rt[z] = T.merge(rt[x], rt[y], 0, n + q); siz[z] = siz[x] + siz[y]; } } return 0; }

欢迎关注

posted @   David24  阅读(112)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示