题解:P2266 爱的供养

link

这道题要使得从一个点出发经过大小不小于 \(T\) 的连通块且要使最大的边权最小。

考虑使用 kruskal 重构树。我们建好 kruskal 重构树之后,其实就是要求包含叶子节点的数量不小于 \(T\) 且包含询问点子树的根的最小权值。

由于我们建的 kruskal 重构树是大根堆性质,所以就是求最深的那个节点。

树上倍增即可。


cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const long long N = 7.2e5 + 5;
struct Edge {long long u, v, w;} e[720005];
long long h[605][605], f[N], val[N], fa[21][N], sz[N]; // sz[u] 表示 u 的字树有多少叶子
long long tot = 0, v, cnt, root;
vector<long long> g[N];
long long getf(long long x) {return x == f[x] ? x : f[x] = getf(f[x]);}
void kruskal() {
    sort(e + 1, e + 1 + tot, [](Edge x, Edge y) {return x.w < y.w;});
    for(long long i = 1; i <= v; i++) f[i] = i;
    for(long long i = 1; i <= tot; i++) {
        long long u = getf(e[i].u), v = getf(e[i].v);
        if(u == v) continue;
        val[++cnt] = e[i].w; // 建立新点,权值为边权
        f[cnt] = f[u] = f[v] = cnt;
        g[cnt].push_back(u), g[cnt].push_back(v);
        fa[0][u] = fa[0][v] = cnt;
    }
    root = getf(1);
}
void dfs(long long u) { // 求倍增表
    if(u <= v) sz[u] = 1;
    for(long long i = 1; i < 21; i++) fa[i][u] = fa[i - 1][fa[i - 1][u]];
    for(auto v : g[u]) dfs(v), sz[u] += sz[v];
}
int main() {
    cin.tie(0)->sync_with_stdio(0);
    long long n, m, t; cin >> m >> n >> t;
    v = n * m; cnt = v;
    for(long long i = 1, idx = 1; i <= m; i++)
        for(long long j = 1; j <= n; j++, idx++) { // 网格图建边
            cin >> h[i][j];
            if(i > 1) e[++tot] = {idx, idx - n, abs(h[i][j] - h[i - 1][j])};
            if(j > 1) e[++tot] = {idx, idx - 1, abs(h[i][j] - h[i][j - 1])};
        }
    kruskal(); // 求重构树
    dfs(root); // 倍增
    long long ans = 0;
    for(long long i = 1; i <= v; i++) {
        bool op; cin >> op;
        if(op) {
            long long u = i;
            for(long long j = 20; j >= 0; j--) if(fa[j][u] && sz[fa[j][u]] < t) u = fa[j][u];
            // 树上倍增
            u = (fa[0][u] ? fa[0][u] : u);
            ans += val[u];
        }
    }
    cout << ans << "\n";
    return 0;
}