题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入格式
第一行有两个用一个空格隔开的整数 n,m表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行三个整数 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
这题貌似有很多种解法(克鲁斯卡尔重构树都出来了),我只想出来了最大生成树+LCT T^T
首先比较关键的一点就是想到用最大生成树来做。这其实是比较好想的,因为题目分类在最小生成树里我们注意到生成树能保证图上任意两点可达,同时又希望路径上的边权最小值尽可能大,最大生成树自然也就呼之欲出了。剩下的部分就需要建最大生成树,用树链剖分来查询任意两点之间路径边权的最小值了。当然这个题用到的操作仅仅是查询,但万一会对道路执行扩建呢,比树剖那个模板简单很多。
思路很清晰,但写起来还是有很多要注意的地方的。
1) 最重要的一点就是,注意到整个图可能不完全联通。解决办法就是先判定f[x] == f[y]是否成立。这里有个坑了我半天的地方,就是建最大生成树树之后一定要对并查集所有的点进行一遍路径压缩!!这样才能保证同一集合内元素所“显示”的代表元是一致的!
2) 其次就是LCT模板题中是点权,而这个题给的是边权,需要我们把边权转化为点权。这其实也很好想,对于一颗有根树,直接把连接父亲和儿子的那条边的边权赋给儿子的点权即可。这可以在树剖预处理的那个dfs1函数内完成。这样会造成两个问题:第一就是要么把根节点的权值设置为无限大,要么在查询区间内排除掉根节点,保证不会影响答案。第二就是在询问的时候最后要这样写:return min(ans, ask(1, dfn[x] + 1, dfn[y]));从dfn[x]+1开始。因为比如
1—2—3—4这四个点之间的边权实际上分别给了2, 3, 4的点权;和1无关,因此要dfn[x] + 1。
#include <bits/stdc++.h> #define N 10005 using namespace std; struct rec { int x; int y; int z; } e[50005]; bool cmp(rec a, rec b)//注意是构建最大生成树 { return a.z > b.z; } int n, m, f[N]; int get(int x) { if(x == f[x]) return x; return f[x] = get(f[x]); } set<int> s; int size[N], son[N], top[N], dep[N], fa[N], dfn[N], wt[N], head[N], ver[N * 2], Next[N * 2], edge[2 * N], w[N], tot = 0, cnt = 0, q; void add(int x, int y, int z) { ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot; } /*----------下为线段树----------*/ struct SegmentTree { int l; int r; int mmin; } t[4 * N]; void build(int p, int l, int r) { t[p].l = l; t[p].r = r; if(l == r) { t[p].mmin = wt[l];//无效模可能会超时? return; } int mid = (l + r) >> 1; build(2 * p, l, mid); build(2 * p + 1, mid + 1, r); t[p].mmin = min(t[2 * p].mmin, t[2 * p + 1].mmin); } int ask(int p, int l, int r) { if(t[p].l >= l && t[p].r <= r) { return t[p].mmin; } int mid = (t[p].l + t[p].r) >> 1; int val = 0x3f3f3f3f; if(l <= mid) val = min(val, ask(2 * p, l, r)); if(r > mid) val = min(val, ask(2 * p + 1, l, r)); return val; } /*----------下为树剖预处理----------*/ void dfs1(int x, int f, int deep)//x为当前节点, f为父亲, deep为深度 { dep[x] = deep; fa[x] = f; size[x] = 1;//记录每个非叶子节点的子树大小(包括自己) for(int i = head[x]; i; i = Next[i]) { int y = ver[i], z = edge[i]; if(y == f) continue; w[y] = z;//边权转化为点权 dfs1(y, x, deep + 1); size[x] += size[y]; if(size[y] > size[son[x]]) son[x] = y;//更新重儿子 } } void dfs2(int x, int topf)//topf是当前链最顶端的节点 { dfn[x] = ++cnt; wt[cnt] = w[x]; top[x] = topf; if(!son[x]) return; dfs2(son[x], topf);//保证重链连续性, 先搜索重儿子 for(int i = head[x]; i; i = Next[i]) { int y = ver[i]; if(y == fa[x] || y == son[x]) continue; dfs2(y, y);//对于每个轻儿子有一条从他自己开始的链 } } /*----------下为树剖查询----------*/ inline int qRange(int x, int y) { int ans = 0x3f3f3f3f; while(top[x] != top[y])//当两个点还没跳到同一条链上 { if(dep[top[x]] < dep[top[y]]) swap(x, y); ans = min(ans, ask(1, dfn[top[x]], dfn[x]));; x = fa[top[x]]; } //直到两个点处于同一条链上 if(dep[x] > dep[y]) swap(x, y); return min(ans, ask(1, dfn[x] + 1, dfn[y])); //因为是点权转边权 dfn[x]不能算进去,得加一 } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) f[i] = i; for(int i = 1; i <= m; i++) { scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].z); } sort(e + 1, e + m + 1, cmp); for(int i = 1; i <= m; i++) { int x = get(e[i].x), y = get(e[i].y); if(x == y) continue; add(e[i].x, e[i].y, e[i].z); add(e[i].y, e[i].x, e[i].z); f[x] = y; } for(int i = 1; i <= n; i++) { int temp = get(i);//一定要先全部路径压缩 } //设1为最小生成树的根节点 w[1] = 0x3f3f3f3f; dfs1(1, 0, 1); dfs2(1, 1); build(1, 1, n); cin >> q; for(int i = 1; i <= q; i++) { int x, y; scanf("%d%d", &x, &y); int ans = qRange(x, y); if(f[x] == f[y]) cout << ans <<endl;//理论上不用判定f[x]==f[y]啊 不知道为啥 else cout << -1 <<endl; } return 0; }