传送门 一道树链剖分边权转点权好题 虽然可以用倍增 LCA 做 题意 A 国有 nnn 座城市,编号从 111 到 nnn,城市之间有 mmm 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 qqq 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。 思路 很显然要先跑一遍最大生成树,尽量让所有的城市之间以更大限重的道路连接。 然后整个图就变成一棵树了,所以直接用树链剖分处理,找出两点之间路径上最小权值就好了。 注意图有不连通的情况,所以还需要在进行树链剖分时把所有没有访问到的点都 DFS 一遍。 接下来的重头戏就是边权转点权的处理了。 显然一个边只连着一个儿子,但可能多条边连在一个父亲上,所以考虑把边权直接转到儿子的点权值上。 就像下图这样。 然后就这么交上去 WA 成狗 其实我们可以在查询时很轻松地 hack 掉原来的查询方法。 如下图。 如果我们要查询点 4→54\to 54→5 路径上的最小权值,我们的输出会是 233,而实际上最小权值应是 256. 显然在 4→54\to 54→5 路径上是没有 1→21\to 21→2 这条边的,但经过了 222 这个点,导致 1→21\to 21→2 的边权值被计算在内。 如何处理这种情况呢? 只需要在最后,两点 u,v 在同一条重链上时,查询 pos[u] + 1 和 pos[v] 区间的最小值就可以了。 这个方法适用于所有边权转点权的题。 完整代码 cpp复制代码#include <bits/stdc++.h> using namespace std; const int maxN = 1e5 + 5; const int INF = 0x3f3f3f3f; struct Edge { int next, to, val; } edge[maxN << 1]; struct Edg { int from, to, val; bool operator < (const Edg &rhs) const { return val > rhs.val; } } edg[maxN]; struct node { int min; } tree[maxN << 2]; int top[maxN], fa[maxN], dep[maxN], par[maxN]; int pos[maxN], siz[maxN], head[maxN]; int w[maxN], val[maxN]; bool vis[maxN]; int cnt, n, q, m, tim; int find(int x) { return par[x] == x ? x : par[x] = find(par[x]); } bool same(int x, int y) { return find(x) == find(y); } void unite(int x, int y) { if (!same(x, y)) par[find(y)] = par[x]; } void add(int from, int to, int val) { edge[++cnt] = (Edge) {head[from], to, val}; head[from] = cnt; } void kruskal() { int num = 0; for (int i = 1; i <= m; i++) if (!same(edg[i].from, edg[i].to)) { int u = edg[i].from, v = edg[i].to; unite(u, v); num++; add(u, v, edg[i].val); add(v, u, edg[i].val); if (num == n - 1) break; } } void dfs1(int u) { siz[u] = 1, dep[u] = dep[fa[u]] + 1, vis[u] = true; for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (v != fa[u]) { w[v] = edge[i].val; // 边权转点权 fa[v] = u; dfs1(v); siz[u] += siz[v]; } } } void dfs2(int u) { int maxn = 0, nxt = -1; pos[u] = ++tim; for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (v != fa[u] && siz[v] > maxn) maxn = siz[v], nxt = v; } if (nxt == -1) return; top[nxt] = top[u]; dfs2(nxt); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (v != fa[u] && v != nxt) { top[v] = v; dfs2(v); } } } struct SegmentTree { #define ls (o << 1) #define rs (o << 1 | 1) #define mid ((l + r) >> 1) void build(int o, int l, int r) { if (l == r) { tree[o].min = val[l]; return; } build(ls, l, mid); build(rs, mid + 1, r); tree[o].min = min(tree[ls].min, tree[rs].min); } int query(int o, int l, int r, int sl, int sr) { if (sl <= l && r <= sr) return tree[o].min; int ret = INF; if (sl <= mid) ret = min(ret, query(ls, l, mid , sl, sr)); if (sr > mid) ret = min(ret, query(rs, mid + 1, r, sl, sr)); return ret; } } T; int query_path(int u, int v) { int ret = INF; while (top[u] != top[v]) { if (dep[top[u]] > dep[top[v]]) swap(u, v); ret = min(ret, T.query(1, 1, n, pos[top[v]], pos[v])); v = fa[top[v]]; } if (pos[u] > pos[v]) swap(u, v); ret = min(ret, T.query(1, 1, n, pos[u] + 1, pos[v])); // 在查询时要 pos[u] 要 +1 return ret; } int main() { memset(head, -1, sizeof(head)); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) par[i] = i; for (int i = 1, num = 0; i <= m; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); edg[++num] = (Edg) {a, b, c}; } sort(edg + 1, edg + 1 + m); kruskal(); for (int i = 1; i <= n; i++) if (!vis[i]) { dfs1(i); top[i] = i; dfs2(i); } for (int i = 1; i <= n; i++) val[pos[i]] = w[i]; T.build(1, 1, n); scanf("%d", &q); for (int i = 1, a, b; i <= q; i++) { scanf("%d%d", &a, &b); if (!same(a, b)) printf("-1\n"); else printf("%d\n", query_path(a, b)); } return 0; }