货车运输 noip2013 luogu P1967 (最大生成树+倍增LCA)

luogu题目传送门!

首先,题目让我们求每个货车的最大运输量,翻译一下就是求路径上边权最小的边。

利用一下贪心思想可知,所有货车肯定都会尽量往大的边走。

进一步翻译,即为有一些小边货车根本不会走,或者说,我们只用知道点与点之间最大的连通路径就好了。

!!!

那么,我们求一下最大生成树,就可以知道最大连通图了。

然后来到第二步:求出了最大生成树,即树上点与点的路径只有一条,这条路也肯定是符合题目要求的。

那么,如何求出路径上最小的边?
暴力DFS?FLOYD?SPFA?

最快也是O(N*P)的复杂度让你通通T掉!

因此,面对如此多的询问,考虑在线维护的做法,不考虑问一次遍历一次的做法。

于是,众神仙便想出了倍增的做法:

在倍增的时候维护一个w, 和倍增的f类似, w应该存储指向w方向的最小边。

每次询问时,快速倍增取min就好了。

#include <bits/stdc++.h>
using namespace std;
#define N 500010
#define INF (~0u>>1)

inline int read(){
    int x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-')s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s; 
}

struct edge{ // 临时存边 
    int u, v, dis;
} e[N];

struct node{
    int u, v, w;
    int next;
} t[N];
int head[N];
int fa[N]; // 并查集 
int  n, m;

int bian = 0;
inline void add(int u, int v, int w){
    t[++bian].u = u;
    t[bian].v = v;
    t[bian].w = w;
    t[bian].next = head[u];
    head[u] = bian;
    return ;
}

bool cmp(edge a, edge b){
    return a.dis > b.dis;
}

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

inline void kruskal(int n, int m){ // 求最大生成树 
    sort(e + 1, e + m + 1, cmp);
    for(int i = 1;i <= n; i++)
        fa[i] = i;   // 并查集初始化 
    for(int i = 1;i <= m; i++){
        int fau = find(e[i].u), fav = find(e[i].v);
        int u = e[i].u, v = e[i].v;
        if(fau != fav){
            fa[fau] = fav;
            add(u, v, e[i].dis);
            add(v, u, e[i].dis);
        }
    }
    return ;
}

namespace LCA{
    int deth[N], f[N][21];
    int w[N][21];
    bool vis[N];
    
    void dfs(int now){
        vis[now] = 1;
        for(int i = head[now]; i; i = t[i].next){
            int v = t[i].v, u = t[i].u,dis = t[i].w;
            if(!vis[v]){
                deth[v] = deth[now] + 1;
                w[v][0] = dis; // w 储存指向v的边权, 在 LCA 初始化时应变成 LCA 路径上的最小值 
                f[v][0] = now; // f0 即为自己的上一个 
                dfs(v);
            }
        }
        return ;
    }
    
    int lca(int x, int y){
        if(find(x) != find(y))return -1; // 两个点不连通 
        int ans = INF;
        if(deth[x] > deth[y])swap(x, y);
        for(int i = 20; i >= 0;i--){
            if(deth[f[y][i]] >= deth[x]){
                ans = min(ans, w[y][i]);
                y = f[y][i]; // 先拉近距离 
            }
        }
        if(x == y)return ans;
        for(int i = 20;i >= 0; i--){//优先跳大的 
            if(f[x][i] != f[y][i]){
                ans = min(ans, min(w[x][i], w[y][i])); // 更新 
                x = f[x][i];
                y = f[y][i];//上跳
            }
        }
        ans = min(ans, min(w[x][0], w[y][0])); // 更新 
        return ans;
    }
    
    void main(){
        for(int i = 1;i <= n;i++){ // 恶心数据可能不连通 
            if(!vis[i]){
                deth[i] = 1;
                dfs(i);//第一次储存信息 
                w[i][0] = INF;// 开头,不可能有 w 
                f[i][0] = i;// 开头,本不配有 f。 因此存成自己 
            }
        }
        for(int i = 1;i <= 20; 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]);//LCA初始化, w应保存 LCA路上指向j点方向的最小边 
            }
        }
        int q = read();
        for(int i = 1;i <= q; i++){
            int x = read(), y = read();
            printf("%d
", lca(x, y));
        }
        return ;
    }
    
}

int main(){
    n = read(), m = read();
    for(int i = 1;i <= m; i++){
        int x = read(), y = read(), z = read();
        e[i].u = x;
        e[i].v = y;
        e[i].dis = z;//第一次存储 
    }
    kruskal(n, m);//求最大生成树 
    LCA::main();
    return 0;
}

其实还有别的解法:  我的洛谷博客

原文地址:https://www.cnblogs.com/wondering-world/p/12679122.html