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;
}

posted @ 2020-07-26 16:33  Shadow_hyc  阅读(90)  评论(0)    收藏  举报