P3332 [ZJOI2013] K大数查询 题解

Description

你需要维护 nn 个可重整数集,集合的编号从 11nn
这些集合初始都是空集,有 mm 个操作:

  • 1 l r c:表示将 cc 加入到编号在 [l,r][l,r] 内的集合中
  • 2 l r c:表示查询编号在 [l,r][l,r] 内的集合的并集中,第 cc 大的数是多少。

注意可重集的并是不去除重复元素的,如 {1,1,4}{5,1,4}={1,1,4,5,1,4}\{1,1,4\}\cup\{5,1,4\}=\{1,1,4,5,1,4\}

1n,m5×1041 \le n,m \le 5\times 10^4

Solution

这里考虑一个在线做法。

首先显然需要用到树套树。由于树套树不能下传标记,所以考虑标记永久化。

具体地,对于这 nn 个可重集维护一颗线段树,在线段树上的每个节点 [l,r][l,r] 都维护一个值域线段树,表示在 [l,r][l,r] 上每个修改的数打的标记的次数。

那么可以得到一个做法:修改时先在修改区间 [l,r][l,r] 所有线段树每个分拆区间上打一次标记。查询时则把询问区间 [L,R][L,R] 的所有分拆区间的祖先节点(包括自己) [l0,r0][l_0,r_0] 加入 vector,权值为 min(R,r0)max(L,l0)+1\min(R,r_0)-\max(L,l_0)+1,然后在这 O(logn)O(\log n) 个值域线段树上二分。

但是这么做是不对的,因为询问时的分拆区间 [l0,r0][l_0,r_0] 的子树内(去除自己)的标记是没有算到的,而这些没有算到的标记的权值均为其所在节点的区间长度,所以还需要维护第二个树套树,表示每个节点 [l,r][l,r] 子树内的标记对 [l,r][l,r] 的贡献,这个在修改时维护即可。

具体见代码。

时间复杂度:O(mlog2n)O(m\log^2n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e4 + 5, kMaxT = 2e7 + 5;

struct Node {
int ls, rs, cnt;
} t[kMaxT];

int n, m, sgt_cnt;
int rt1[kMaxN * 4], rt2[kMaxN * 4];

void update(int &x, int l, int r, int ql, int v) {
if (!x) x = ++sgt_cnt;
t[x].cnt += v;
if (l == r) return;
int mid = (l + r) >> 1;
if (ql <= mid) update(t[x].ls, l, mid, ql, v);
else update(t[x].rs, mid + 1, r, ql, v);
}

void update_arr(int x, int l, int r, int ql, int qr, int c) {
if (l > qr || r < ql) return;
if (l >= ql && r <= qr) {
update(rt1[x], -n, n, c, 1);
return;
}
update(rt2[x], -n, n, c, std::min(r, qr) - std::max(l, ql) + 1);
int mid = (l + r) >> 1;
update_arr(x << 1, l, mid, ql, qr, c), update_arr(x << 1 | 1, mid + 1, r, ql, qr, c);
}

void getpos(int x, int l, int r, int ql, int qr, std::vector<std::pair<int, int>> &vec) {
if (l > qr || r < ql) return;
if (rt1[x]) vec.emplace_back(rt1[x], std::min(r, qr) - std::max(l, ql) + 1);
if (l >= ql && r <= qr) {
if (rt2[x]) vec.emplace_back(rt2[x], 1);
return;
}
int mid = (l + r) >> 1;
getpos(x << 1, l, mid, ql, qr, vec), getpos(x << 1 | 1, mid + 1, r, ql, qr, vec);
}

int query(int l, int r, int64_t c) {
std::vector<std::pair<int, int>> vec;
getpos(1, 1, n, l, r, vec);
int L = -n, R = n;
for (; L != R;) {
int mid = (L + R) >> 1;
int64_t crs = 0;
for (auto [x, w] : vec) crs += 1ll * w * t[t[x].rs].cnt;
if (c <= crs) {
L = mid + 1;
for (auto &[x, w] : vec) x = t[x].rs;
} else {
c -= crs, R = mid;
for (auto &[x, w] : vec) x = t[x].ls;
}
}
return L;
}

void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= m; ++i) {
int op, l, r; int64_t c;
std::cin >> op >> l >> r >> c;
if (op == 1) update_arr(1, 1, n, l, r, c);
else std::cout << query(l, r, c) << '\n';
}
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P3332 [ZJOI2013] K大数查询 题解
https://sobaliuziao.github.io/2025/03/15/post/4029eda.html
作者
Egg_laying_master
发布于
2025年3月15日
许可协议