1. 程式人生 > 實用技巧 >Luogu P1967 貨車運輸

Luogu P1967 貨車運輸

思路

這道題確實有含金量,值得一做。先說一下我的做題過程。

這個題本來第一眼是想用Prim+樹剖LCA來做的,但是發現如果用Prim跑最大生成樹的話做重構樹會極其困難。捨棄。

然後想用Kruskal+樹剖LCA做。但是我又悲催地發現用樹剖LCA難以統計邊權的最小值(雖然快啊啊啊)。捨棄。

最後才是Kruskal+倍增LCA。

這個題表面上看好像就是把最大生成樹的板子和LCA的板子套在了一起,但是事實遠沒有看起來那麼簡單。

這個題的難點並不在於最大生成樹和重構樹上,而是在於如何高效地找出兩點間路徑上的權值的最小值(其實這個可以用樹鏈剖分套線段樹來做,但是我不會)。顯然如果你先通過某種方法求出了LCA,再

去遍歷求路徑上的最小值是不現實的。那麼我們就要考慮在求LCA的過程中求出兩點間路徑的最小值。

我們考慮維護一個數組w,w[i][j]表示從i向上跳j步經過的路徑的權值最小是什麼。這個東西和f陣列的維護方法大同小異,沒啥特別的(這是對於倍增LCA來說的)。其他的部分就套個板子就行了。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define MAXN 10050
#define MAXM 50050
#define INF 0x7fffffff
#define DEBUG puts("OK");
int n, m, q;
int fa[MAXN];
int head[MAXN], cnt;
int dep[MAXN], f[MAXN][23];
int w[MAXN][23];
bool vis[MAXN];
struct node_graph{
    int from, to;
    int val;
} graph[MAXM];
struct node_tree{
    int nxt, to;
    int val;
} tree[MAXN << 1];
inline int read(void){
    int f = 1, x = 0;char ch;
    do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
    do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
    return f * x;
}
inline void add_edge_tree(int x,int y,int z){
    ++cnt;
    tree[cnt].nxt = head[x];
    tree[cnt].to = y;
    tree[cnt].val = z;
    head[x] = cnt;
    return;
}
inline int _min(int x,int y){
    return x < y ? x : y;
}
inline void _swap(int &x,int &y){
    int tmp = x;
    x = y, y = tmp;
    return;
}
inline bool cmp(const node_graph a,const node_graph b){
    return a.val > b.val;
}
inline int get_father(int x){
    return fa[x] == x ? x : fa[x] = get_father(fa[x]);
}
void Kruskal(void){
    int tot = 0;
    std::sort(graph + 1, graph + m + 1, cmp);
    for (int i = 1; i <= n;++i)
        fa[i] = i;
    for (int i = 1; i <= m;++i){
        int u = get_father(graph[i].from), v = get_father(graph[i].to);
        if(u==v) continue;
        fa[u] = v, ++tot;
        add_edge_tree(u, v, graph[i].val);
        add_edge_tree(v, u, graph[i].val);
        if(tot==n-1) break;
    }
    return;
}
void DFS(int k){
    vis[k] = 1;
    for (int i = head[k]; i; i = tree[i].nxt){
        int v = tree[i].to;
        if(vis[v]) continue;
        dep[v] = dep[k] + 1;
        f[v][0] = k;
        w[v][0] = tree[i].val;
        DFS(v);
    }
    return;
}
inline void _init(void){
    for (int i = 1; i <= n;++i){
        if(!vis[i]){
            dep[i] = 1;DFS(i);
            f[i][0] = i, w[i][0] = INF;
        }
    }
    for (int i = 1; i <= 21;++i){
        for (int j = 1; j <= n;++j){
            f[j][i] = f[f[j][i - 1]][i - 1];
            w[j][i] = _min(w[j][i - 1], w[f[j][i - 1]][i - 1]);
        }
    }
    return;
}
inline int LCA(int x,int y){
    if(get_father(x)!=get_father(y))
        return -1;
    int res = INF;
    if(dep[x]>dep[y]) _swap(x, y);
    for (int i = 20; i >= 0;--i){
        if(dep[f[y][i]]>=dep[x]){
            res = _min(res, w[y][i]);
            y = f[y][i];
        }
    }
    if(x==y) return res;
    for (int i = 21; i >= 0;--i){
        if(f[x][i]!=f[y][i]){
            res = _min(res, _min(w[y][i], w[x][i]));
            x = f[x][i], y = f[y][i];
        }
    }
    res = _min(res, _min(w[x][0], w[y][0]));
	return res;
}
int main(){
    n = read(), m = read();
    for (int i = 1; i <= m;++i)
        graph[i].from = read(), graph[i].to = read(), graph[i].val = read();
    Kruskal();
    _init();
    q = read();
    for (int i = 1; i <= q;++i){
        int x = read(), y = read();
        printf("%d\n", LCA(x, y));
    }
    return 0;
}