20260516 - 部分分训练 1
目前分数:65pts
Joler老师分数:80pts
这是一道 NOI 题,所以有很多部分分。
老师想让我们在死磕的基础上拿到更多的分。
题目描述
对于一个序列,定义其众数为序列中出现次数严格大于一半的数字。注意该定义与一般的定义有出入,在本题中请以题面中给出的定义为准。
一开始给定 \(n\) 个长度不一的正整数序列,编号为 \(1 \sim n\),初始序列可以为空。这 \(n\) 个序列被视为存在,其他编号对应的序列视为不存在。
有 \(q\) 次操作,操作有以下类型:
- \(1 \ x \ y\):在 \(x\) 号序列末尾插入数字 \(y\)。保证 \(x\) 号序列存在,且 \(1 \le x, y \le n + q\)。
- \(2 \ x\):删除 \(x\) 号序列末尾的数字,保证 \(x\) 号序列存在、非空,且 \(1 \le x \le n + q\)。
- \(3 \ m \ x_1 \ x_2 \dots \ x_m\):将 \(x_1, x_2, \ldots, x_m\) 号序列顺次拼接,得到一个新序列,并询问其众数。如果不存在满足上述条件的数,则返回 \(-1\)。数据保证对于任意 \(1 \le i \le m\),\(x_i\) 是一个仍然存在的序列,\(1 \le x_i \le n + q\),且拼接得到的序列非空。注意:不保证 \(\boldsymbol{x_1, \ldots, x_m}\) 互不相同,询问中的合并操作不会对后续操作产生影响。
- \(4 \ x_1 \ x_2 \ x_3\):新建一个编号为 \(x_3\) 的序列,其为 \(x_1\) 号序列后顺次添加 \(x_2\) 号序列中数字得到的结果,然后删除 \(x_1, x_2\) 对应的序列。此时序列 \(x_3\) 视为存在,而序列 \(x_1, x_2\) 被视为不存在,在后续操作中也不会被再次使用。保证 \(1 \le x_1, x_2, x_3 \le n + q\)、\(x_1 \ne x_2\)、序列 \(x_1, x_2\) 在操作前存在、且在操作前没有序列使用过编号 \(x_3\)。
数据范围
对于所有测试数据,保证 \(1 \le n, q, C_m, C_l \le 5 \times {10}^5\)。
| \(n, q\) | \(C_m, C_l\) | 测试点编号 | 特殊性质 A | 特殊性质 B | 特殊性质 C |
|---|---|---|---|---|---|
| \(\le 300\) | \(\le 300\) | \(1 \sim 3\) | 否 | 否 | 是 |
| \(\le 4000\) | \(\le 4000\) | \(4 \sim 7\) | 否 | 否 | 是 |
| \(\le {10}^5\) | \(\le {10}^5\) | \(8\) | 是 | 是 | 是 |
| \(\le {10}^5\) | \(\le {10}^5\) | \(9\) | 是 | 否 | 否 |
| \(\le {10}^5\) | \(\le {10}^5\) | \(10\) | 否 | 是 | 否 |
| \(\le {10}^5\) | \(\le {10}^5\) | \(11 \sim 12\) | 否 | 否 | 是 |
| \(\le {10}^5\) | \(\le {10}^5\) | \(13\) | 否 | 否 | 否 |
| \(\le 5 \times {10}^5\) | \(\le 5 \times {10}^5\) | \(14\) | 是 | 是 | 是 |
| \(\le 5 \times {10}^5\) | \(\le 5 \times {10}^5\) | \(15\) | 是 | 否 | 否 |
| \(\le 5 \times {10}^5\) | \(\le 5 \times {10}^5\) | \(16\) | 否 | 是 | 否 |
| \(\le 5 \times {10}^5\) | \(\le 5 \times {10}^5\) | \(17 \sim 18\) | 否 | 否 | 是 |
| \(\le 5 \times {10}^5\) | \(\le 5 \times {10}^5\) | \(19 \sim 20\) | 否 | 否 | 否 |
特殊性质 A:保证 \(n = 1\) 且没有操作 \(4\)。
特殊性质 B:保证任意时刻任何序列中只有数字 \(1\) 和 \(2\)。
特殊性质 C:保证没有操作 \(2\)。
部分分分析
显然,要离线询问。
暴力
显然,按照题目模拟。
{
// BL
array<int, 10010> cnt{};
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
a[x].pop_back();
} else if (op == 3) {
int sumSZ = 0;
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
sumSZ += a[x].size();
for (auto k : a[x]) {
++cnt[k];
}
}
int pos = max_element(cnt.begin(), cnt.end()) - cnt.begin();
if (cnt[pos] * 2 > sumSZ) printf("%d\n", pos);
else puts("-1");
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
for (auto k : a[x]) {
--cnt[k];
}
}
} else {
int x1 = qy[1], x2 = qy[2], x3 = qy[3];
a[x3].swap(a[x1]);
for (auto v : a[x2]) a[x3].push_back(v);
}
}
}
性质 B
因为只有两种数,所以开两个桶去算。
但是会 TLE,因为暴力合并会 T 飞。
所以要启发式合并,顺序的问题用 deque 就可以了。
事实证明不用开 long long 和 list。
if (markB) {
vector<array<int, 3>> cnt(n + q + 1, array<int, 3>{});
for (int i = 1; i <= n; i++) {
for (auto j : a[i]) {
++cnt[i][j];
}
}
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
++cnt[x][y];
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
--cnt[x][a[x].back()];
a[x].pop_back();
} else if (op == 3) {
ll l1 = 0, l2 = 0;
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
l1 += cnt[x][1];
l2 += cnt[x][2];
}
if (l1 > l2) puts("1");
else if (l2 > l1) puts("2");
else puts("-1");
} else {
int x1 = qy[1], x2 = qy[2], x3 = qy[3];
if (a[x1].size() > a[x2].size()) {
a[x3].swap(a[x1]);
cnt[x3].swap(cnt[x1]);
for (auto v : a[x2]) a[x3].push_back(v);
cnt[x3][1] += cnt[x2][1];
cnt[x3][2] += cnt[x2][2];
} else {
a[x3].swap(a[x2]);
cnt[x3].swap(cnt[x2]);
for (auto v : a[x1]) a[x3].push_front(v);
cnt[x3][1] += cnt[x1][1];
cnt[x3][2] += cnt[x1][2];
}
}
}
return 0;
}
性质 A
因为只有一个组数,所以可以开一个线段树记录最大值。
if (n == 1 && markA) {
const int maxl = 1e6;
for (int i = 1; i <= n; i++) {
for (auto j : a[i]) {
change(1, 1, maxl, j, 1);
}
}
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
change(1, 1, maxl, y, 1);
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
change(1, 1, maxl, a[x].back(), -1);
a[x].pop_back();
} else if (op == 3) {
auto ans = query(1, 1, maxl, 1, maxl);
if (ans.maxv * 2 > (int)a[1].size())
printf("%d\n", ans.id);
else
puts("-1");
} else {
assert(0);
}
}
return 0;
}
性质 C
还没写出来。
就是用一个叫做摩尔投票的东西。
模板在这里。
然后用一些奇奇妙妙的方法合并。
然后就没有然后了。
PS:好像可以线段树的时候维护。
65pts 代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define inf (1 << 30)
#define lnf (1LL << 60)
#define all(x) (x).begin(), (x).end()
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7, Q = 5e5 + 7;
constexpr int P = 998244353;
int n, q;
struct Info {
int maxv, id;
};
Info operator + (const Info &l, const Info &r) {
Info a;
a.maxv = max(l.maxv, r.maxv);
if (a.maxv == l.maxv) a.id = l.id;
else a.id = r.id;
return a;
}
struct Node {
Info val;
} seg[(N + Q) * 4];
void update(int id) {
seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}
void change(int id, int l, int r, int pos, int val) {
if (l == r) {
seg[id].val.maxv += val;
seg[id].val.id = l;
} else {
int mid = (l + r) / 2;
if (pos <= mid) change(id * 2, l, mid, pos, val);
else change(id * 2 + 1, mid + 1, r, pos, val);
update(id);
}
}
Info query(int id, int l, int r, int ql, int qr) {
if (l == ql && r == qr) return seg[id].val;
int mid = (l + r) / 2;
if (qr <= mid) return query(id * 2, l, mid, ql, qr);
else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}
int main() {
bool markA = true, markB = true, markC = true;
scanf("%d%d", &n, &q);
vector<deque<int>> a(n + q + 1);
for (int i = 1; i <= n; i++) {
int l;
scanf("%d", &l);
a[i].resize(l);
for (int j = 0; j < l; j++) {
scanf("%d", &a[i][j]);
if (a[i][j] > 2) markB = false;
}
}
vector<vector<int>> qry(q);
for (int i = 0; i < q; i++) {
int op;
scanf("%d", &op);
if (op == 1) {
int x, y;
scanf("%d%d", &x, &y);
if (y > 2) markB = false;
qry[i] = {op, x, y};
} else if (op == 2) {
markC = false;
int x;
scanf("%d", &x);
qry[i] = {op, x};
} else if (op == 3) {
int m;
scanf("%d", &m);
qry[i] = {op, m};
for (int j = 0; j < m; j++) {
int x;
scanf("%d", &x);
qry[i].push_back(x);
}
} else {
markA = false;
int x1, x2, x3;
scanf("%d%d%d", &x1, &x2, &x3);
qry[i] = {op, x1, x2, x3};
}
}
if (markB) {
vector<array<int, 3>> cnt(n + q + 1, array<int, 3>{});
for (int i = 1; i <= n; i++) {
for (auto j : a[i]) {
++cnt[i][j];
}
}
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
++cnt[x][y];
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
--cnt[x][a[x].back()];
a[x].pop_back();
} else if (op == 3) {
ll l1 = 0, l2 = 0;
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
l1 += cnt[x][1];
l2 += cnt[x][2];
}
if (l1 > l2) puts("1");
else if (l2 > l1) puts("2");
else puts("-1");
} else {
int x1 = qy[1], x2 = qy[2], x3 = qy[3];
if (a[x1].size() > a[x2].size()) {
a[x3].swap(a[x1]);
cnt[x3].swap(cnt[x1]);
for (auto v : a[x2]) a[x3].push_back(v);
cnt[x3][1] += cnt[x2][1];
cnt[x3][2] += cnt[x2][2];
} else {
a[x3].swap(a[x2]);
cnt[x3].swap(cnt[x2]);
for (auto v : a[x1]) a[x3].push_front(v);
cnt[x3][1] += cnt[x1][1];
cnt[x3][2] += cnt[x1][2];
}
}
}
return 0;
}
if (n == 1 && markA) {
const int maxl = 1e6;
for (int i = 1; i <= n; i++) {
for (auto j : a[i]) {
change(1, 1, maxl, j, 1);
}
}
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
change(1, 1, maxl, y, 1);
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
change(1, 1, maxl, a[x].back(), -1);
a[x].pop_back();
} else if (op == 3) {
auto ans = query(1, 1, maxl, 1, maxl);
if (ans.maxv * 2 > (int)a[1].size())
printf("%d\n", ans.id);
else
puts("-1");
} else {
assert(0);
}
}
return 0;
}
{
// BL
array<int, 10010> cnt{};
for (auto qy : qry) {
int op = qy[0];
if (op == 1) {
int x = qy[1], y = qy[2];
a[x].push_back(y);
} else if (op == 2) {
int x = qy[1];
a[x].pop_back();
} else if (op == 3) {
int sumSZ = 0;
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
sumSZ += a[x].size();
for (auto k : a[x]) {
++cnt[k];
}
}
int pos = max_element(cnt.begin(), cnt.end()) - cnt.begin();
if (cnt[pos] * 2 > sumSZ) printf("%d\n", pos);
else puts("-1");
for (int j = 2; j < (int)qy.size(); j++) {
int x = qy[j];
for (auto k : a[x]) {
--cnt[k];
}
}
} else {
int x1 = qy[1], x2 = qy[2], x3 = qy[3];
a[x3].swap(a[x1]);
for (auto v : a[x2]) a[x3].push_back(v);
}
}
}
return 0;
}
30 发罚时。。。

浙公网安备 33010602011771号