The 2021 ICPC Asia Shanghai Regional Programming Contest H. Life is a Game 题解 Kruskal重构树

题目链接:https://codeforces.com/gym/103446/problem/H

题目大意:

人生就是一场游戏。

世界可以看作是 \(n\) 个城市和城市之间 \(m\) 条无向道路的无向连通图。

现在你,生命游戏玩家,将在世界图表上玩生命游戏。最初,您位于第 \(x\) 个城市和第 \(k\) 个社交能力点。你可以通过生活和工作来获得社交能力点数。

具体来说,您可以通过在第 \(i\) 个城市生活和工作来赚取 \(a_i\) 社交能力点数。但在这个问题中,你不能在一个城市重复获得社交能力点数。所以你想环游世界,获得更多的社交能力点数。然而,道路并不容易。具体来说,第 \(i\) 条道路有一个能力阈值 \(w_i\) ,你至少要有 \(w_i\) 的社交能力点数才能通过这条路。此外,通过道路时,您的社交能力点数不会减少,但如果您想通过第 \(i\) 个道路,则至少需要 \(w_i\) 个社交能力点数奥德。正如你所看到的,生活游戏就是重复地生活、工作和旅行。有 \(q\) 个游戏保存。

每次保存游戏时,都会给出初始城市和社交能力点,并且玩家没有在任何城市生活或工作过。现在你,现实生活中的游戏玩家,需要确定在游戏结束时你可以拥有的社交能力点数的最大可能数量,并为每个给定的游戏保存输出它。

解题思路:

Kruskal重构树 基础练习题。

思路参考自官方题解:https://codeforces.com/gym/103446/attachments/download/14828/LiyuuCute.pdf

这里主要要讲一下就是怎么倍增:

在 kruskal 重构的树中,

对于当前节点 \(u\) 来说,设它的父节点是 \(p\),只有在满足 \(u\) 对应的节点权值和 \(+k\) \(\ge\) 节点 \(p\) 对应的边权时,才能从 \(u\) 走到 \(p\)

所以本题的关键体现在代码中的 \(ff\) 数组,

ff[u][i] 表示 \(u\) 能往上(即祖先节点那个方向)走 \(2^i\) 所需的最小的 \(k\) 是多少。

这一部分是我感觉最需要思考的地方(其它感觉都还好)。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5, maxm = 2e5 + 5;

int n, m, q, f[maxn];
int fa[maxn][17], tr[maxn], idx, dep[maxn], val[maxn];
vector<int> g[maxn];

void init() {
    for (int i = 1; i < 2*n; i++) {
        f[i] = fa[i][0] = i;
        g[i].clear();
    }
    idx = n;
}

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

struct Edge {
    int u, v, w;

    bool operator < (const Edge &b) const {
        return w < b.w;
    }
} e[maxm];

void dfs(int u, int d) {
    dep[u] = d;
    for (auto v : g[u])
        dfs(v, d+1);
}

void kruskal_build_tree() {
    init();
    sort(e, e+m);
    for (int i = 0; i < m; i++) {
        int u = e[i].u, v = e[i].v, w = e[i].w;
        int x = find(u), y = find(v);
        if (x != y) {
            int z = ++idx;
            tr[z] = w;
            fa[z][0] = z;
            g[z].push_back(x);
            g[z].push_back(y);
            fa[x][0] = fa[y][0] = z;
            f[x] = f[y] = z;
            val[z] = val[x] + val[y];
        }
    }
    for (int i = 1; i <= idx; i++)
        if (fa[i][0] == i)
            dfs(i, 0);

    for (int i = 1; i <= 16; i++)
        for (int u = 1; u <= idx; u++)
            fa[u][i] = fa[ fa[u][i-1] ][i-1];
}


//int cal(int x, int k) { // 暴力cal会TLE
//    while (fa[x][0] != x) {
//        int p = fa[x][0];
//        if (k + val[x] >= tr[p])
//            x = p;
//        else
//            break;
//    }
//    return k + val[x];
//}

int ff[maxn][20];
int before_cal() {
    for (int u = 1; u <= idx; u++) {
        if (fa[u][0] == u) {
            ff[u][0] = 2e9;
        }
        else {
            int p = fa[u][0];
            ff[u][0] = tr[p] - val[u];
        }
    }
    for (int i = 1; i <= 16; i++)
        for (int u = 1; u <= idx; u++)
            ff[u][i] = max(ff[u][i-1], ff[ fa[u][i-1] ][i-1]);
}

int cal(int x, int k) {
    for (int i = 16; i >= 0; i--) {
        if (k >= ff[x][i]) {
            x = fa[x][i];
        }
    }
    return k + val[x];
}


int main() {
    while (~scanf("%d%d%d", &n, &m, &q)) {
        for (int i = 1; i <= n; i++) {
            scanf("%d", val+i);
        }
        for (int i = 0; i < m; i++)
            scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
        kruskal_build_tree();
        before_cal();
        while (q--) {
            int x, k;
            scanf("%d%d", &x, &k);
            int ans = cal(x, k);
            printf("%d\n", ans);
        }
    }

    return 0;
}
posted @ 2025-03-21 22:24  quanjun  阅读(64)  评论(0)    收藏  举报