题解 P1967 【货车运输】
题目描述
A国有n座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入输出格式
输入格式:
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行3个整数 x, y, z,每两个整数之间用一个空格隔开,表示从 x号城市到 y号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。
输出格式:
共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出−1。
输入输出样例
输入样例#1:
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例#1: 复制
3
-1
3
说明
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。
主要思路: Kruskal重构树 + LCA
这个题的问题是求一棵树,使得两点之间的简单路径中最小边权最大
这是一个Kruskal重构树的一个基础问题,就是快速求得最小生成树上的边权最值问题。
这里是重构一棵树,像之前一样跑Kruskal,只是在每次选边时把选上的边都新建一个节点,这个节点的点权值是这个边的边权,然后把这两个点与新建节点连边,每次连边同时用并查集把新建节点一起维护。
当连完边后,并不是说直接dfs建树,而是要讨论一个问题:可能图不连通,所以这时可能是一个森林,所以每个联通块都要建树,如果不在一个联通块里,直接“-1”。
code:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;
#define go(i, j, n, k) for(int i = j; i <= n; i += k)
#define fo(i, j, n, k) for(int i = j; i >= n; i -= k)
#define rep(i, x) for(int i = h[x]; i; i = e[i].nxt)
#define mn 200010
#define inf 1 << 30
#define ll long long
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch > '9' || ch < '0') { if(ch == '-') f = -f; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
// 记得开2倍数组啊,因为Kruskal重构树有最多2n - 1个节点
// 此处光荣的采用树剖LCA(逃
struct kr{
int x, y, w;
} ee[mn << 1];
inline bool cmp(kr a, kr b) {
return a.w > b.w;
}
struct edge{
int v, nxt;
} e[mn << 1];
int h[mn], p, w[mn];
inline void add(int a, int b) {
e[++p].nxt = h[a], h[a] = p, e[p].v = b;
}
inline void aadd(int i, int a, int b, int c) {
ee[i].x = a, ee[i].y = b, ee[i].w = c;
// cout << a << " " << b << " " << c << "\n";
}
// edge and node --------------------
int n, m, father[mn << 1], tot, cnt, q, vis[mn];
inline int findx(int x) {
if(x == father[x]) return x;
else return father[x] = findx(father[x]);
}
// union-find set -------------------
int fa[mn], sze[mn], top[mn], son[mn], dep[mn];
void dfs1(int x, int f, int deep) {
fa[x] = f;
sze[x] = 1;
dep[x] = deep;
vis[x] = 1;
int maxson = -1;
rep(i, x) {
int v = e[i].v;
if(v == f) continue;
dfs1(v, x, deep + 1);
sze[x] += sze[v];
if(sze[v] > maxson)
maxson = sze[v], son[x] = v;
}
}
void dfs2(int x, int topf) {
top[x] = topf;
if(!son[x]) return;
dfs2(son[x], topf);
rep(i, x) {
int v = e[i].v;
if(v == fa[x] || v == son[x]) continue;
dfs2(v, v);
}
}
inline int LCA(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
// LCA ---------------------------------
inline void Kru() { // 基本差不多的Kruskal
tot = n;
go(i, 1, n, 1) father[i] = i;
sort(ee + 1, ee + m + 1, cmp);
go(i, 1, m, 1) {
int xx = findx(ee[i].x);
int yy = findx(ee[i].y);
if(xx != yy) {
tot++;
// 毕竟是新建节点嘛QwQ
father[tot] = father[xx] = father[yy] = tot;
add(xx, tot);
add(tot, xx);
add(yy, tot);
add(tot, yy);
// 记得连无向边
w[tot] = ee[i].w;
if(++cnt == n - 1)
break;
}
}
go(i, 1, tot, 1) { // 森林
if(!vis[i]) {
int f = findx(i);
dfs1(f, 0, 1), dfs2(f, f);
}
}
}
// Kruskal Refactoring Tree ----------------------
int ff[mn];
inline int findf(int x) {
if(x == ff[x]) return ff[x];
else return ff[x] = findf(ff[x]);
}
int used[mn];
int main() {
n = read(), m = read();
go(i, 1, n, 1) ff[i] = i;
go(i, 1, m, 1) {
int a = read(), b = read(), c = read();
aadd(i, a, b, c);
int aa = findf(a), bb = findf(b);
if(rand() % 2) ff[aa] = bb; // 随机合并大发吼啊
else ff[bb] = aa;
}
Kru();
q = read();
go(i, 1, q, 1) {
int x = read(), y = read();
int xx = findx(x), yy = findx(y);
if(xx != yy) {
printf("-1\n");
continue;
} else {
printf("%d\n", w[LCA(x, y)]);
}
}
return 0;
}