「BZOJ-2733」[HNOI2012]永无乡-STL pb-ds tree启发式合并

永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的。现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥。Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪 座,请你输出那个岛的编号。

链接

BZOJ-2733

题解

首先对于一个连通块中,询问我们可以直接用平衡树来求第 $k$ 大,那么我们可以用并查集来维护各个块中的连通情况。

所以我们可以直接使用 pb_ds tree 来完成求第 $k$ 大,这里就需要平衡树的启发式合并了,值得注意的是 pb_ds tree 提供 join 方法,$A.join(B)$ 即把 B 并入 A,这个操作对于 splay_tree_tag 来说,复杂度是 $O(logn)$ 的(其实并没有 $O(log^2n)$ 的 rb_tree_tag 快),但是这个方法的使用前提是值域不相交,所以我们只能自己写启发式合并,启发式合并了听起来很高端,其实就是判断两颗树的 size,把小的往大的里面插,对于 STL 来说就更简单了,直接遍历小树,然后对着大的 insert,然后 clear 小树,时间复杂度未知,据说红黑树启发式合并出现了值域交叉的总复杂度为 $O(m log (\frac {n} {m} + 1))$,而使用 rb_tree_tagpb_ds_tree。实际上跑的相当快,在内存动态的劣势下,仍然只用了 1184ms,bzoj rk15。

而此题还有一个特殊性,就是在并查集按秩合并的过程中,比较秩的过程与 size 的比较效果是相同的,所以不必再比较一次 size

这种方法同样适用于 setmapunordered_mapcc_hash_tableSTL 容器的合并。

代码

除了输入输出优化,程序相当短…

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
* created by xehoth on 20-04-2017
*/
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
inline char read() {
static const int IN_LEN = 1000000;
static char buf[IN_LEN], *s, *t;
s == t ? t = (s = buf) + fread(buf, 1, IN_LEN, stdin) : 0;
return s == t ? -1 : *s++;
}
template<class T>
inline void read(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return;
c == '-' ? iosig = true : 0;
}
for (x = 0; isdigit(c); c = read())
x = (x + (x << 2) << 1) + (c ^ '0');
iosig ? x = -x : 0;
}
const int OUT_LEN = 1000000;
char obuf[OUT_LEN], *oh = obuf;
inline void print(char c) {
oh == obuf + OUT_LEN ? (fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf) : 0;
*oh++ = c;
}
template<class T>
inline void print(T x) {
static int buf[30], cnt;
if (x == 0) {
print('0');
} else {
if (x < 0) print('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) print((char)buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
namespace Task {
const int MAXN = 100005;
int fa[MAXN], rank[MAXN];
typedef __gnu_pbds::tree<int, int, std::less<int>, __gnu_pbds::rb_tree_tag, __gnu_pbds::tree_order_statistics_node_update> Tree;
Tree t[MAXN];
inline int get(int x) {
register int p = x, i;
while (p != fa[p]) p = fa[p];
while (p != x) i = fa[x], fa[x] = p, x = i;
return p;
}
inline void put(int x, int y) {
if ((x = get(x)) != (y = get(y))) {
rank[x] > rank[y] ? (std::swap(x, y), 0) : 0;
fa[x] = y;
for (Tree::iterator it = t[x].begin(); it != t[x].end(); it++)
t[y][it->first] = it->second;
rank[x] == rank[y] ? rank[y]++ : 0;
t[x].clear();
}
}
inline int query(int x, int k) {
return k > t[x = get(x)].size() ? -1 : t[get(x)].find_by_order(k - 1)->second;
}
inline void solve() {
register int n, m, q;
read(n), read(m);
for (register int i = 1, a; i <= n; i++)
read(a), fa[i] = i, t[i][a] = i;
for (register int i = 1, x, y; i <= m; i++)
read(x), read(y), put(x, y);
read(q);
register char c;
while (q--) {
c = read();
while (isspace(c)) c = read();
switch (c) {
case 'B':
read(n), read(m), put(n, m);
break;
case 'Q':
read(n), read(m);
print(query(n, m)), print('\n');
break;
}
}
}
}
int main() {
Task::solve(), flush();
return 0;
}

分享到