并查集+二分 hnu13433 Dragons

该博客讨论了一道涉及到并查集和二分法的算法问题。题目描述了N个城市、M条双向边和K条龙的情况,要求确定最少需要多少勇士才能在不限时间的情况下消灭所有龙。博主分析了问题的关键在于处理不完全连通的图,并提出通过并查集找到连通分量,然后采用二分查找策略来确定每个连通分量所需的最小勇士数量。二分验证函数涉及对超过勇士数量的龙在第一分钟内直接消灭。
摘要由CSDN通过智能技术生成

传送门:点击打开链接

题意:有N个城市,M条双向边,K条龙。

第i条龙在Ci城市,初始有Si个头,如果龙还活着,每一分钟增加Ci个头

一个勇士1分钟可以砍一个龙的头,也可以选择移动到当前城市周围相邻的城市去

不限定时间的前提下,至少要召集多少勇士,才能把所有的龙全部杀掉

勇士的初始位置可以是任意的 


思路:这道题出的非常好,但是却一下子很难想。

首先,可以想到,这个图可能并不完全连通,那么就会有很多个连通分量,这些连通分量之间没有联系,那么勇士就不能跨连通分量转移,所以对于多个连通分量,肯定是要肯开处理,然后积累总和的。

其次,怎样才能将龙给杀掉呢?有两种办法。在第1秒的时候,就直接有Si个勇士,直接把初始血量打成0,就杀了。或者是,在整个连通分量中勇士的个数超过了Ni的前提下,肯定能将这条龙杀了,因为时间是无限的,我只要召集整个连通分量中的勇士,然后每分钟造成的伤害比回复快,那么肯定是可以的。

想到这里,我们就能想到了,之所以告诉你边,完全只是要你分别求出连通分量而已。。所以我们使用并查集将n个点分开成多个连通分量。

对于一个连通分量中,,到底应该派多少勇士呢。因为有两种决策,然后当时卡死在这里,,最初想利用贪心策略,但是想了很久都没有构造出来,后来发现如果去二分其实特别简单。那么如何二分呢。二分的对象是,对于这个连通分量中,至少需要m个勇士,才能将这个连通分量中的龙全部杀掉。那么对于Ni+1<=m的那些龙,肯定是可以杀死的,因为上面的策略2嘛。对于Ni+1>m的龙,如果想只用m个勇士就能把这条龙杀了的话,那么就必须需要Si个勇士,直接在第一分钟就杀了它。所以,二分中的验证函数,就是积累Ni+1>m的龙的Si之和,与m去做比较,看大小关系即可~

#include<map>
#include<set>
#include<cmath>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck printf("fuck")
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;

const int MX = 2000 + 5;

vector<int>G[MX];
int P[MX], A[MX];

struct Data {
    int C, S, N;
    Data() {}
    Data(int _C, int _S, int _N) {
        C = _C; S = _S; N = _N;
    }
} D[MX];

int find(int x) {
    return P[x] == x ? x : (P[x] = find(P[x]));
}

bool check(int id, int x) {
    int sum = 0;
    for(int i = 0; i < G[id].size(); i++) {
        int v = G[id][i];
        if(x < D[v].N + 1) sum += D[v].S;
    }
    return sum <= x;
}

int main() {
    int n, m, k; //FIN;
    while(~scanf("%d%d%d", &n, &m, &k), n) {
        for(int i = 1; i <= n; i++) {
            P[i] = i;
            G[i].clear();
        }

        for(int i = 1; i <= m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            int p1 = find(u), p2 = find(v);
            P[p1] = p2;
        }

        int rear = 0;
        for(int i = 1; i <= n; i++) {
            if(i == find(i)) A[++rear] = i;
        }

        for(int i = 1; i <= k; i++) {
            int ci, si, ni;
            scanf("%d%d%d", &D[i].C, &D[i].S, &D[i].N);
            int p = find(D[i].C);
            G[p].push_back(i);
        }

        int ans = 0;
        for(int i = 1; i <= rear; i++) {
            int l = 0, r = 1e9, m, ret;
            while(l <= r) {
                m = (l + r) >> 1;
                if(check(A[i], m)) {
                    ret = m;
                    r = m - 1;
                } else l = m + 1;
            }
            ans += ret;
        }
        printf("%d\n", ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值