给出 nn 个点的一棵树,多次询问两点之间的最短距离。
注意:
第一行为两个整数 nn 和 mm。nn 表示点数,mm 表示询问次数;
下来 n−1n−1 行,每行三个整数 x,y,kx,y,k,表示点 xx 和点 yy 之间存在一条边长度为 kk;
再接下来 mm 行,每行两个整数 x,yx,y,表示询问点 xx 到点 yy 的最短距离。
树中结点编号从 11 到 nn。
共 mm 行,对于每次询问,输出一行询问结果。
2≤n≤1042≤n≤104,
1≤m≤2×1041≤m≤2×104,
0
2 2
1 2 100
1 2
2 1
100
100
3 2
1 2 10
3 1 15
1 2
3 2
10
25
在深度优先遍历时,将所有点分成三大类:
已经遍历过的点,且回溯过
正在搜索的分支
还未搜索到的点
代码:
//
// Created by Martin on 2025/5/31.
//
#include
#include
using namespace std;
typedef pair PII;
const int N= 1e4 + 10, M = N << 1;
int n, m, idx;
struct Edge {
int to, w, next;
} edge[M];
int head[N];
int p[N]; // 并查集存储root节点
int res[M]; // M次查询的结果
int st[N]; // tarjan划分, 1是正在搜的,2是搜完回溯的,0是未被搜过的点
int dist[N];
vector query[N]; // 存储查询的请求, First存查询的另一个点,Second查询编号
// 添加边
void add(int a, int b, int w) {
edge[idx].to = b, edge[idx].next = head[a], edge[idx].w = w;
head[a] = idx++;
}
// 预处理距离root的距离
void dfs(int u, int fa) {
for (int i = head[u]; ~i; i = edge[i].next) {
int j = edge[i].to;
if (j == fa) continue; // 如果j == fa,要阻断,防止向上搜索(全联通)
dist[j] = dist[u] + edge[i].w; // 距离
dfs(j, u);
}
}
// 路径压缩-并查集
int find(int x) {
if (p[x] != x) return p[x] = find(p[x]);
return x;
}
void tarjan(int u) {
st[u] = 1; // 开始搜索
for (int i = head[u]; ~i; i = edge[i].next) {
int j = edge[i].to;
if (!st[j]) { // 未被标记
tarjan(j);
p[j] = u;
}
}
for (auto item : query[u]) {
int y = item.first, id = item.second;
if (st[y] == 2) {
int anc = find(y); // y所属的根节点,就是u,y的公共祖先
res[id] = dist[u] + dist[y] - dist[anc] * 2;
}
}
st[u] = 2; // 搜索完u点,标记2
}
int main() {
scanf("%d%d", &n, &m);
fill(head, head + N, -1);
for (int i = 0; i < n - 1; i++) { // 输入n-1条边
int a, b, w;
scanf("%d%d%d", &a,&b,&w);
add(a, b, w); add(b, a, w);
}
// 输入m次查询
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d%d", &a, &b);
if (a != b) {
query[a].push_back({b, i});
query[b].push_back({a, i});
}
}
// 初始化p数组,每一个点首先都是一个集合
for (int i = 1; i <= n; i++) p[i] = i;
// 随便找一个点的当根, 这里选1
int root = 1;
dfs(root, -1);
tarjan(root);
for (int i = 0; i < m; i++) printf("%d\n", res[i]);
return 0;
}