P1967 [NOIP2013 提高组] 货车运输 (生成树,LCA)

发布时间 2023-10-13 19:17:18作者: Sakana~

P1967 [NOIP2013 提高组] 货车运输

https://www.luogu.com.cn/problem/P1967
首先有些边是没用的(比较小的边),比如两个点之间的两条(并行的)路,只有较大的会被走到,小的不会被走,因此可以直接去除小的边,即求最大生成树。
接着做求任意两点经过的边的最小值就演变成求树上任意两点的最小树边,这个可以通过倍增求LCA实现,具体就是在倍增的时候用一个数组记录一下最值就行。\(f_{i, k}\) 表示 \(i\) 点往上跳 \(2^k\) 步所经过的最小边
注意图不一定连通

#include <bits/stdc++.h>

using namespace std;
const int N = 1e4 + 5, M = 1e5 + 5;
int n, m, q, fa[N], dep[N], f[N][30], dp[N][30];
int h[N], e[M], ne[M], w[M], idx;
bool vis[N];

void add (int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

struct Node {
    int a, b, w;
    bool operator<(const Node &t) const {
        return w > t.w;
    }
}p[M];

int find (int x) {
    if (x != fa[x]) fa[x] = find (fa[x]);
    return fa[x];
}

void kruscal(){
    sort (p + 1, p + m + 1);
    for (int i = 1; i <= n; i ++)    fa[i] = i;
    for (int i = 1; i <= m; i ++){
        int a = p[i].a, b = p[i].b, w = p[i].w;
        a = find(a), b = find(b);
        if (a != b)    fa[a] = b, add (a, b, w), add (b, a, w);// cout << a << ' ' << b << ' ' << w << endl;
    }
}

void dfs (int x) {
    vis[x] = true;
    for (int i = h[x]; ~i; i = ne[i]) {
        int j = e[i];
        if (vis[j]) continue;
        dep[j] = dep[x] + 1;
        f[j][0] = x, dp[j][0] = w[i];
        dfs (j);
    }
}

int lca (int a, int b) {
    if (find (a) != find (b))   return -1;
    if (dep[a] < dep[b])    swap (a, b); //确保a往上跳
    int ans = 1e9;
    for (int k = 20; k >= 0; k--) {
        if (dep[f[a][k]] >= dep[b]) { //注意是>=
            ans = min (ans, dp[a][k]), a = f[a][k];
        }
    }
    if (a == b)     return ans;
    for (int k = 20; k >= 0; k--) {
        if (f[a][k] != f[b][k]) {
            ans = min ({ans, dp[a][k], dp[b][k]});
            a = f[a][k], b = f[b][k];
        }
    }
    ans = min ({ans, dp[a][0], dp[b][0]});
    return ans;
}

int main () {
    memset (h, -1, sizeof h);
    scanf ("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        scanf ("%d%d%d", &a, &b, &c);
        p[i] = {a, b, c};
    }
    kruscal (); //最大生成树
    //memset (dp, 0x3f, sizeof dp);
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;
        dep[i] = 1, dfs (i);
        f[i][0] = i, dp[i][0] = 0x3f3f3f3f;
    }
    for (int i = 1; i <= 20; i++) {
        for (int j = 1; j <= n; j++) {
            f[j][i] = f[f[j][i-1]][i-1];
            dp[j][i] = min (dp[j][i-1], dp[f[j][i-1]][i-1]);
        }
    }
    scanf ("%d", &q);
    while (q--) {
        int a, b;
        scanf ("%d%d", &a, &b);
        printf ("%d\n", lca (a, b));
    }
}