题目链接
题解
思路
两个操作,对于第一个操作——合并,需要使用并查集;对于第二个操作——查询,使用树状数组。
我们使用树状数组维护一个前缀和。数组下标表示人数,元素表示在树状数组的意义下(其本质并非前缀和),小于等于该人数的帮派个数。
当我们需要查询第k
个帮派的人数时,利用树状数组求和函数sum
,二分地在树状数组中查询。查询的初始区间为[1, n]
。若求和函数的返回值大于等于k
,则说明应该在[l, mid]
中查询,否则应在[mid, r]
中查询。由于这样的查询结果是第k
小的帮派人数(升序排列第k
个),与题意不符,所以我们在查询之前需要对k
进行处理,使其查询升序排列的情况下第Num - k + 1
个帮派人数。其中Num
为当前帮派的人数。
其中求和函数sum(i)
返回的值是真正意义上的前缀和,即人数小于等于i
的帮派的数量。
在具体的实现过程中,需要注意:
- 并查集的查询操作需要使用路径压缩。
AC代码
#include <bits/stdc++.h>
using namespace std;
int const N = 1e5 + 10;
int tree[N], a[N], par[N];
int num;
int n, m;
int lowBit(int x) {
return x & (-x);
}
//int find(int x) {
// while (x != par[x]) x = par[x];
// return x;
//}
int find(int x) {
int tmp;
while (par[x] != x) {
tmp = x;
x = par[x];
par[tmp] = par[x];
}
return x;
}
int sum(int x) {
int ret = 0;
while (x > 0) {
ret += tree[x];
x -= lowBit(x);
}
return ret;
}
void add(int pos, int val) {
while (pos <= n) {
tree[pos] += val;
pos += lowBit(pos);
}
}
void Join(int x, int y) {
x = find(x), y = find(y);
if (x != y) {
par[y] = x;
add(a[x], -1); // 人数为a[x]或a[y]的帮派数量减一,树状数组所维护的前缀和中包含数量a[x]或a[y]的数组元素值同样减一。
add(a[y], -1);
a[x] += a[y]; // 更新帮派人数数组中的值。
a[y] = 0; // 被吞并后数量为零。
add(a[x], 1); // 新帮派人数a[x]。
num -= 1; // 帮派总数量减一。
}
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) par[i] = i, a[i] = 1;
memset(tree, 0, sizeof(tree));
add(1, n); // 初始化树状数组,人数为 1 的帮派数量有 n 个。
num = n;
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int x, y;
scanf("%d%d", &x, &y);
Join(x, y);
} else if (op == 2) {
int k;
scanf("%d", &k);
if (k > num) {
printf("-1\n");
continue;
} else {
// 查询操作。
k = num - k + 1;
int l = 1, r = n;
while (l < r) {
int mid = (l + r) >> 1;
if (sum(mid) >= k) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
}
}
}
}
return 0;
}