货车运输
题目描述
A 国有 n n n 座城市,编号从 1 1 1 到 n n n,城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 q q q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入格式
第一行有两个用一个空格隔开的整数 $ n,m$,表示 A 国有 $ n$ 座城市和 m m m 条道路。
接下来
m
m
m 行每行三个整数
x
,
y
,
z
x, y, z
x,y,z,每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为
z
z
z 的道路。
注意:
x
≠
y
x \neq y
x=y,两座城市之间可能有多条道路 。
接下来一行有一个整数 q q q,表示有 q q q 辆货车需要运货。
接下来 q q q 行,每行两个整数 x , y x,y x,y,之间用一个空格隔开,表示一辆货车需要从 x x x 城市运输货物到 y y y 城市,保证 x ≠ y x \neq y x=y。
输出格式
共有
q
q
q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出
−
1
-1
−1。
样例
样例输入
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
样例输出
3
-1
3
提示
对于 30 % 30\% 30% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1≤n<1000, 1 ≤ m < 10 , 000 1 \le m < 10,000 1≤m<10,000, 1 ≤ q < 1000 1\le q< 1000 1≤q<1000;
对于 60 % 60\% 60% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1≤n<1000, 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1≤m<5×104, 1 ≤ q < 1000 1 \le q< 1000 1≤q<1000;
对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 4 1 \le n < 10^4 1≤n<104, 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1≤m<5×104,$1 \le q< 3\times 10^4 $, 0 ≤ z ≤ 1 0 5 0 \le z \le 10^5 0≤z≤105。
笨蒟蒻的个人题解
传送门
这题是一道非常经典的题目,当然本题解法不止一种,起码有三四种,所以这个题非常经典。但并不是说题解方法很多就很简单,相反,这题不简单了。
题意见图,比如说从 1 1 1 号点走到 3 3 3 号点,它有两种走法:
1、 1 − > 2 − > 3 1 -> 2 -> 3 1−>2−>3
2、 1 − > 3 1 -> 3 1−>3
那么这种第一种走法负载量是 3 3 3,第二种走法负载量是 1 1 1,所以我选第一种。
ok,这就是这道题的含义。
怎么思考?
显然这个图有一个边是没有用处的,那就是那个边长为 1 1 1 的那个,因为从 1 1 1 到 3 3 3 已经可以联通了,并且是最大的,所以那个边长为 1 1 1 的就不需要了,无论如何都不会经过 1 1 1 那个边,既然没用,就可以删掉,那么一直删一直删,直到不能删为止,你会惊奇的发现这成了一颗树,为什么呢?因为两点之间最多只有一条路径了,而那个路径恰恰就是你要求的路径的最小值!那么这个时候自然而然地就可以想到,这个时候就需要找最大生成树!然后去重构!这是第一个要想到的突破点!找最大生成树有两种方法,一种是 prim,一种是 Kruskal,这道题不能用 prim,因为开不了这么大的数组,而且在时间上还会爆(堆优化的另说,没试过)!所以得要用 Kruskal,那么这个算法复杂度是 O ( m l o g m ) O(mlogm) O(mlogm)。通过这个算法进行树的重构,把有环的问题变成树的问题!
重构树这是第一步要想到的,然后接着想,怎么去想求在一个无根树上的一条线上的最小值呢?
就以这个图为例子吧,比如说 4 4 4 和 6 6 6 吧,它的沿线是: 4 − > 2 − > 1 − > 5 − > 6 4 -> 2 -> 1 -> 5 -> 6 4−>2−>1−>5−>6。很容易看出来最小值是 3 3 3,对吧。当然你可以用 dfs/bfs 一个结点一个结点去搜索,这样的话我们来分析一下复杂度,时间复杂度是 O ( m q ) O(mq) O(mq),显然超时了,所以不可以,要再去优化!
how?
我们可以这样,既然这个是一个无根树,对吧,那么我们可以先认一个结点做根节点,然后做 lca,找最近公共祖先,那么答案就是两个结点到最近公共祖先的值的最小值,做 lca 的同时还要去维护一下路径最小值(我新开了个数组 ww,这里和倍增跳的思路一样),这里还是推荐用倍增法,tarjan 离线确实是不太好写。还有维护深度和最小值的时候最好用 dfs 吧,因为它不止一颗树,还是不建议用 bfs(因为它可能有些结点没有,我是 wa 了才改成 dfs 的)。
ok,那这个题就做完了,献上笨蒟蒻写了两个小时的代码(主要是码量有点多,太难调了,总出问题)。
(2023 年 5 月 12 日写的,今天 7 月 3 日回顾一下)。。。
AC_Code
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
typedef pair<int, int>PII;
const int N = 5e4 + 10, M = N * 2;
int n, m, Q;
struct Edge
{
int u, v, w;
bool operator<(const Edge& t) const
{
return t.w < w;
}
}edge[N];
int p[N], depth[N], fa[N][18], ww[N][18];
int e[M], ne[M], w[M], h[N], idx;
bool st[N];
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void Kruskal()
{
sort(edge, edge + m);
for(int i = 0; i < N; i++) p[i] = i;
for(int i = 0; i < m; i++)
{
int u = edge[i].u, v = edge[i].v, z = edge[i].w;
// cout << '-' << z << endl;
if(find(u) != find(v))
{
p[find(u)] = find(v);
add(u, v, z), add(v, u, z);
}
}
}
/*
void bfs(int root)
{
memset(dep, inf, sizeof(dep));
dep[0] = 0, dep[root] = 1;
int hh = 0, tt = 0;
q[0] = root;
while(hh <= tt)
{
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(dep[j] > dep[t] + 1)
{
dep[j] = dep[t] + 1;
q[++tt] = j;
fa[j][0] = t;
//if(j == 3) cout << w[i] << endl;
ww[j][0] = w[i];
}
}
}
}
*/
void dfs(int u, int f)
{
st[u] = true;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == f) continue;
if(st[j]) continue;
depth[j] = depth[u] + 1;
fa[j][0] = u;
ww[j][0] = w[i];
dfs(j, u);
}
}
void lca_init()
{
for(int k = 1; k < 18; k++)
{
for(int j = 1; j <= n; j++)
{
fa[j][k] = fa[fa[j][k - 1]][k - 1];
ww[j][k] = min(ww[j][k - 1], ww[fa[j][k - 1]][k - 1]);
//cout << "j = " << j << " k = " << k << " " <<ww[j][k] << endl;
}
}
}
int lca(int a, int b)
{
if(find(a) != find(b)) return -1;
if(depth[a] < depth[b]) return lca(b, a);
// cout << a << " " << b << endl << endl;
int res = inf;
for(int k = 17; k >= 0; k--)
if(depth[fa[a][k]] >= depth[b])
{
// cout << "a = " << a << " k = " << k << " " << ww[a][k] << endl;
res = min(res, ww[a][k]);
a = fa[a][k];
}
if(a == b) return res;
for(int k = 17; k >= 0; k--)
{
if(fa[a][k] != fa[b][k])
{
res = min({res, ww[a][k], ww[b][k]});
a = fa[a][k];
b = fa[b][k];
}
}
res = min({res, ww[a][0], ww[b][0]});
return res;
}
void solve()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
//memset(ww, inf, sizeof(ww));
for(int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edge[i] = {a, b, c};
}
Kruskal();
for(int i = 1; i <= n; i++)
if(!st[i])
{
// depth[i] = 1;
dfs(i, -1);
fa[i][0] = i;
ww[i][0] = inf;
}
//bfs(1);
lca_init();
cin >> Q;
while(Q--)
{
int a, b;
cin >> a >> b;
cout << lca(a, b) << endl;
}
}
signed main()
{
int T = 1;
// cin >> T;
while(T--)
solve();
return 0;
}