B-GT‘s Dream “科林明伦杯“哈尔滨理工大学第八届程序设计竞赛

题目链接

GT’s Dream

题解

思路

两个操作,对于第一个操作——合并,需要使用并查集;对于第二个操作——查询,使用树状数组。

我们使用树状数组维护一个前缀和。数组下标表示人数,元素表示在树状数组的意义下(其本质并非前缀和),小于等于该人数的帮派个数。

当我们需要查询第k个帮派的人数时,利用树状数组求和函数sum,二分地在树状数组中查询。查询的初始区间为[1, n]。若求和函数的返回值大于等于k,则说明应该在[l, mid]中查询,否则应在[mid, r]中查询。由于这样的查询结果是第k小的帮派人数(升序排列第k个),与题意不符,所以我们在查询之前需要对k进行处理,使其查询升序排列的情况下第Num - k + 1个帮派人数。其中Num为当前帮派的人数。

其中求和函数sum(i)返回的值是真正意义上的前缀和,即人数小于等于i的帮派的数量。

在具体的实现过程中,需要注意:

  1. 并查集的查询操作需要使用路径压缩。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值