距离 点的距离,LCA 和tarjan离线LCA

//tarjan 离线lca
// 距离.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

/*

https://www.acwing.com/problem/content/1173/
http://ybt.ssoier.cn:8088/problem_show.php?pid=1552
给定一棵 n 个点的树,Q 个询问,每次询问点 x 到点 y 两点之间的距离。

【输入】
第一行一个正整数 n,表示这棵树有 n 个节点;

接下来 n−1 行,每行两个整数 x,y表示 x,y 之间有一条连边;

然后一个整数 Q,表示有 Q 个询问;

接下来 Q 行每行两个整数 x,y 表示询问 x 到 y 的距离。

【输出】
输出 Q 行,每行表示每个询问的答案。

【输入样例】
6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6
【输出样例】
3
4
【提示】
数据范围与提示:

对于全部数据,1≤n≤105,1≤x,y≤n
*/



#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;

const int N = 100005;      // 最大节点数
const int M = 2 * N;       // 最大边数(无向边要乘2)

// 链式前向星存图相关数组
int head[N];               // head[u]表示u的第一条边的编号
int e[M];                 // to[i]表示第i条边的终点
int nxt[M];                // nxt[i]表示第i条边的下一条边的编号
int idx;                   // 边的总数(即当前可用的边的编号)

int n, Q;                  // n为节点数,Q为询问数
vector<pair<int, int>> query[N]; // 存每个点的所有询问,pair<另一个点, 询问编号>
int res[N];                // 存每个询问的答案
int dist[N];               // dist[i]表示i到根节点的距离
int p[N];                  // 并查集数组
int st[N];                 // 0:未访问, 1:正在访问, 2:已访问

// 并查集查找祖先
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 链式前向星加边函数(无向边要加两次)
void add(int a, int b) {
    e[idx] = b;           // 边的终点
    nxt[idx] = head[a];    // 下一条边指向原来的第一条边
    head[a] = idx++;       // 更新第一条边为当前边
}

// 预处理每个点到根的距离
void dfs(int u, int fa) {
    for (int i = head[u]; i != -1; i = nxt[i]) { // 遍历u的所有邻接点
        int v = e[i];
        if (v == fa) continue;                   // 跳过父节点,防止回头
        dist[v] = dist[u] + 1;                   // 距离加1
        dfs(v, u);                               // 递归处理子节点
    }
}

// Tarjan离线LCA算法
void tarjan(int u) {
    st[u] = 1;                                   // 标记u正在访问
    for (int i = head[u]; i != -1; i = nxt[i]) { // 遍历u的所有邻接点
        int v = e[i];
        if (!st[v]) {                            // 只访问未访问过的子节点
            tarjan(v);                           // 递归访问
            p[v] = u;                            // 回溯时将子节点的祖先设为当前节点
        }
    }
    // 处理所有以u为端点的询问
    for (auto& q : query[u]) {
        int y = q.first, id = q.second;
        if (st[y] == 2) {                        // 另一个点已访问完
            int anc = find(y);                   // 查询LCA
            res[id] = dist[u] + dist[y] - 2 * dist[anc]; // 距离公式
        }
    }
    st[u] = 2;                                   // 标记u访问完成
}

int main() {
    scanf("%d", &n);
    memset(head, -1, sizeof head);               // 初始化head数组为-1
    idx = 0;                                     // 初始化边的编号
    for (int i = 1; i < n; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);                               // 加边(无向边要加两次)
        add(b, a);
    }
    scanf("%d", &Q);
    for (int i = 0; i < Q; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        if (a != b) {
            query[a].push_back({ b, i });        // 双向存储询问
            query[b].push_back({ a, i });
        }
    }
    for (int i = 1; i <= n; i++) p[i] = i;       // 初始化并查集
    memset(st, 0, sizeof st);                    // 初始化状态数组
    dist[1] = 0;                                 // 根节点距离为0
    dfs(1, -1);                                  // 预处理距离
    tarjan(1);                                   // 求LCA并回答所有询问
    for (int i = 0; i < Q; i++) {
        printf("%d\n", res[i]);                  // 输出答案
    }
    return 0;
}
// 1171. 距离.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

using namespace std;

/*
https://ac.nowcoder.com/acm/contest/968/A
https://loj.ac/d/588


https://www.acwing.com/problem/content/description/1173/


题目描述
给定一棵n个点的树,Q个询问,每次询问点x到点y两点之间的距离。
输入描述:
第一行一个正整数n,表示这棵树有n个节点;
接下来n-1行,每行两个整数x,y表示x,y之间有一条连边;
然后一个整数Q,表示有Q个询问;
接下来Q行每行两个整数x,y表示询问x到y的距离。
输出描述:
输出Q行,每行表示每个询问的答案。


6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6


输出样例:
3
4

链接:https://ac.nowcoder.com/acm/contest/968/A


对于全部数据,

1≤n≤10^5,1≤x,y≤n。
*/


#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

const int N = 100010;   // 最大点数
const int LOG = 17;     // 2^17 > 1e5,足够覆盖所有深度

int n, Q;
vector<int> g[N];       // 邻接表存树
int depth[N];           // depth[u]:u到根的深度
int fa[N][LOG];         // fa[u][k]:u的第2^k级祖先

// 预处理深度和倍增祖先
void dfs(int u, int father) {
    fa[u][0] = father;  // u的父亲
    for (int k = 1; k < LOG; k++)
        fa[u][k] = fa[fa[u][k - 1]][k - 1]; // u的2^k级祖先
    for (int v : g[u]) {
        if (v == father) continue;      // 不走回头路
        depth[v] = depth[u] + 1;        // 子节点深度+1
        dfs(v, u);
    }
}

// 倍增法求LCA
int lca(int a, int b) {
    if (depth[a] < depth[b]) swap(a, b); // 保证a不浅于b
    // 先把a跳到和b同一深度
    for (int k = LOG - 1; k >= 0; k--)
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    if (a == b) return a;                // b就是a祖先
    // 一起往上跳,直到LCA
    for (int k = LOG - 1; k >= 0; k--)
        if (fa[a][k] != fa[b][k])
            a = fa[a][k], b = fa[b][k];
    return fa[a][0];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n;
    // 读入树的边
    for (int i = 1; i < n; i++) {
        int x, y;
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }

    // 以1为根节点,预处理深度和祖先
    depth[1] = 0;
    dfs(1, 0);

    cin >> Q;
    while (Q--) {
        int x, y;
        cin >> x >> y;
        int anc = lca(x, y); // 最近公共祖先
        // 距离 = 深度之和 - 2*LCA深度
        cout << depth[x] + depth[y] - 2 * depth[anc] << '\n';
    }
    return 0;
}

posted on 2025-07-03 18:20  itdef  阅读(7)  评论(0)    收藏  举报

导航