上海2021ICPC H Life is a Game (Kruskal重构树)

题目大意:给定n个点,m条边,每个点上有一个点权,每条边有一个边权;走到这个点后你的能力会增加这个点权,你的能力需要大于一条边的边权才可以走到这条边。(1<=n,m,q<=10^{5}

解题思路:很明显是先找一个最小生成树,能够以最小的边权走到每个点的预期路径。Kruskal重构树就是在Kruskal最小生成树的基础上延伸的一个算法,它是一个二叉树,原图中的点都是叶子结点,两个叶子结点的父亲是他们两之间的边权,每次选边权最小的两个点,自底向上构造,保证往上的边权非递减。任意两点间的边权是他们的LCA(当然这题可以不需要用到LCA)。最终的结点有2N个,需要开2N的空间。下图是样例的Kruskal重构树。方框的结点是虚点,结点旁边的值是需要走完它的子树能获得的能量值,这个在建树的时候就可以得到了。每次获得一个初始结点,往上跳,这个需要用倍增跳(因为最坏的情况下数据卡到,树接近单链,逐层一步步跳的时间复杂度就是O(nq)了)。如果大于上面的这个边权值,就跳到这个位置,答案等于这个点统计出来的子树能获得的能量值+初始能量值。

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int maxN = 2010;
const int mod = 1e6 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
int p[N << 1], depth[N << 1], fa[N << 1][21];
ll sum[N << 1], a[N], val[N << 1];
int e[N << 2], ne[N << 2], h[N << 1], idx;
struct Node{
    int a, b;
    ll c;
    bool operator<(const Node &A)const{
        return c < A.c;
    }
}v[N];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}
void bfs(int u) {
    queue<int>qu; qu.push(u);
    depth[u] = 1, depth[0] = 0;
    while (!qu.empty()) {
        int u = qu.front(); qu.pop();
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if (depth[v] > depth[u] + 1) {
                depth[v] = depth[u] + 1;
                qu.push(v);
                fa[v][0]= u;
                for (int k = 1; k <= 20; k ++ ) {
                    fa[v][k] = fa[fa[v][k - 1]][k - 1];
                }
            }
        }
    }
}

void solve() {
    memset(h, -1, sizeof(h));
    memset(depth, 0x3f, sizeof(depth));
    int n, m, q; cin >> n >> m >> q;
    for (int i = 1; i <= 2 * n; i ++ ) p[i] = i;
    for (int i = 1; i <= n; i ++ ) cin >> a[i], sum[i] = a[i];
    for (int i = 1; i <= m; i ++ ) {
        int x, y;
        ll z; cin >> x >> y >> z;
        v[i] = {x, y, z};
    }
    sort(v + 1, v + 1 + m);
    int k = n;
    for (int i = 1; i <= m; i ++ ) {
        int x = v[i].a, y = v[i].b;
        ll z = v[i].c;
        x = find(x), y = find(y);
        if (x == y) continue;
        p[x] = p[y] = ++ k;
        val[k] = z;
        add(x, k), add(k, x), add(y, k), add(k, y);
        sum[k] += sum[x] + sum[y];//a的集合值
    }
    for (int i = k; i >= 1; i -- ) if (depth[i] == 0x3f3f3f3f) bfs(i);
    while (q -- ) {
        ll x, ini;
        cin >> x >> ini;
        ll ans = ini + a[x];
        while(x != k) {
            int x1 = x;
            for (int i = 20; i >= 0; i -- ) {
                if (fa[x][i] && val[fa[x][i]] <= ans) {
                    x = fa[x][i];
                }
            }
            if (x1 == x) break;
            ans = sum[x] + ini;
        }

        cout << ans << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int _;
    //cin >> _;
    _ = 1;
    while (_ -- ) {
        solve();
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值