LCA-Tarjan

1171.距离

给出 nn 个点的一棵树,多次询问两点之间的最短距离。

注意:

  • 边是无向的。
  • 所有节点的编号是 1,2,…,n1,2,…,n。
输入格式

第一行为两个整数 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 1≤x,y≤n1≤x,y≤n

输入样例1:
2 2 
1 2 100 
1 2 
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25

Tarjan

在深度优先遍历时,将所有点分成三大类:

  1. 已经遍历过的点,且回溯过

  2. 正在搜索的分支

  3. 还未搜索到的点

代码:

//
// 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;
}

你可能感兴趣的:(算法,数据结构)