2021 icpc 上海 H. Life is a Game

常规做法是kruscal重构树。但是并查集+启发式合并也能很快的AC这题。

给定n个点的点权,m条边的边权,q个询问,每个询问是一开始在点x上,初始能量为k。如果能走到一个点,则k += 点权;如果此时的能量 >= 边权,则能经过这条边。对于每个询问,输出最大能获取的能量。

考虑每加一条边,如果连接了两个连通块 fx 与 fy,则 fx 中某一些能量就能更新,剩下的由于能量不够大所以不能更新。如果对边权从下到大排序的话,那么此时不能更新能量的点,以后肯定再也不能更新了。那么如何得知一个点的能量不能再更新呢?
对于每一个点,建立优先队列:

struct po{
    int val, pos;
    bool operator < (const po &k) const {
        return val > k.val;
    }
};
priority_queue<po> q[N];

q[i]存放的是关于起始点在i的初始能量。如果一个能量能够更新,那么必然有 val + s[fx] >= 边权。反之则不能更新,就写入ans,并且在优先队列中删除。
在q[fx]和q[fy]中删除那些不能更新的后,就该启发式合并了,比较q[fx]和q[fy]的大小,小的往大的身上合并。
一开始我的写法是:访问q[fx]和q[fy]的所有点,能更新的更新后然后放入temp队列中,不能更新的放入ans数组中,直到q[fx]和q[y]都空了,再把temp中的放回q[fy],就完成了合并的操作。这样子的错误在于,访问了所有的点,q[fy]有可能越来越大,所以复杂度达到了n2。
但是能够更新的是不必马上更新的,也就是不用去访问,所以可以启发式合并,复杂度就变为了nlogn。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 1e9 + 7;
struct node{
    int x, y, val;
    bool operator < (const node &k) const {
        return val < k.val;
    }
} a[N];
struct po{
    int val, pos;
    bool operator < (const po &k) const {
        return val > k.val;
    }
};
int fa[N], siz[N];
int s[N], ans[N];
priority_queue<po> q[N];
int getf(int k)
{
    if (k == fa[k]) return k;
    return fa[k] = getf(fa[k]);
}
void add(int x, int val)
{
    while(!q[x].empty()){
        po w = q[x].top();
        if (w.val + s[x] < val) {
            q[x].pop();
            ans[w.pos] = w.val + s[x];
        }
        else break;
    }
}
signed main()
{
    int n, m, ques;
    cin >> n >> m >> ques;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &s[i]);
        fa[i] = i;
        siz[i] = 1;
    }
    for (int i = 1; i <= m; i++) {
        scanf("%lld%lld%lld", &a[i].x, &a[i].y, &a[i].val);
    }
    sort(a + 1, a + 1 + m);
    for (int i = 1; i <= ques; i++) {
        int x, k;
        scanf("%lld%lld", &x, &k);
        q[x].push(po{k, i});
    }
    for (int i = 1; i <= m; i++){
        int fx = getf(a[i].x), fy = getf(a[i].y), val = a[i].val;
        if (fx == fy) continue;
        add(fx, val);
        add(fy, val);
        if (q[fx].size() > q[fy].size()) swap(fx, fy);
        fa[fx] = fy;
        s[fy] += s[fx];
        while(!q[fx].empty()) {
            q[fy].push(q[fx].top());
            q[fx].pop();
        }
    }
    int ff = getf(1);
    while(!q[ff].empty()) {
        po w = q[ff].top();
        q[ff].pop();
        ans[w.pos] = w.val + s[ff];
    }
    for (int i = 1; i <= ques; i++) printf("%lld\n", ans[i]);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值