传送门 一道树链剖分边权转点权好题 虽然可以用倍增 LCA 做
题意
A 国有 $n$ 座城市,编号从 $1$ 到 $n$,城市之间有 $m$ 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 $q$ 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
思路
很显然要先跑一遍最大生成树,尽量让所有的城市之间以更大限重的道路连接。
然后整个图就变成一棵树了,所以直接用树链剖分处理,找出两点之间路径上最小权值就好了。
注意图有不连通的情况,所以还需要在进行树链剖分时把所有没有访问到的点都 DFS 一遍。
接下来的重头戏就是边权转点权的处理了。
显然一个边只连着一个儿子,但可能多条边连在一个父亲上,所以考虑把边权直接转到儿子的点权值上。
就像下图这样。
然后就这么交上去 WA 成狗
其实我们可以在查询时很轻松地 hack 掉原来的查询方法。
如下图。
如果我们要查询点 $4\to 5$ 路径上的最小权值,我们的输出会是 233,而实际上最小权值应是 256.
显然在 $4\to 5$ 路径上是没有 $1\to 2$ 这条边的,但经过了 $2$ 这个点,导致 $1\to 2$ 的边权值被计算在内。
如何处理这种情况呢?
只需要在最后,两点 u,v
在同一条重链上时,查询 pos[u] + 1
和 pos[v]
区间的最小值就可以了。
这个方法适用于所有边权转点权的题。
完整代码
#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;
}