当前位置: 首页 > article >正文

【模板】树算法之LCA(最近公共祖先)

1. Tarjan离线算法

时间复杂度
• 预处理:O(n + Q)Q为查询次数,使用路径压缩的并查集优化)。
总体效率:对大规模离线查询更快。
特点
离线处理:需预先知道所有查询。
• 基于DFS遍历并查集(路径压缩 + 按秩合并)。
• 常数因子较小,适合超大规模查询(如 Q > 1e6)。

示例代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define int long long
#define pb push_back
#define pii pair<int, int>
#define FU(i, a, b) for (int i = (a); i <= (b); ++i)
#define FD(i, a, b) for (int i = (a); i >= (b); --i)
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;

struct UnionFind {
    vector<int> parent, rank, ancestor;
    UnionFind(int n) {
        parent.resize(n + 1);
        rank.resize(n + 1, 0);
        ancestor.resize(n + 1);
        FU(i, 0, n) parent[i] = ancestor[i] = i;
    }
    int find(int u) {
        return parent[u] == u ? u : parent[u] = find(parent[u]);
    }
    void unite(int u, int v) {
        u = find(u), v = find(v);
        if (u == v) return;
        if (rank[u] < rank[v]) parent[u] = v;
        else {
            parent[v] = u;
            if (rank[u] == rank[v]) rank[u]++;
        }
    }
};

void tarjan(int u, int p, vector<vector<int>>& tree, vector<vector<pii>>& queries,
            vector<bool>& visited, UnionFind& uf, vector<int>& ans) {
    visited[u] = true;
    for (int v : tree[u]) {
        if (v != p) {
            tarjan(v, u, tree, queries, visited, uf, ans);
            uf.unite(u, v);
            uf.ancestor[uf.find(u)] = u;
        }
    }
    for (auto [v, idx] : queries[u]) {
        if (!visited[v] || ans[idx] != -1) continue;
        ans[idx] = uf.ancestor[uf.find(v)];
    }
}

signed main() {
    cin.tie(0)->ios::sync_with_stdio(0);
    int n, m, s;
    cin >> n >> m >> s;
    vector<vector<int>> tree(n + 1);
    FU(i, 1, n - 1) {
        int u, v;
        cin >> u >> v;
        tree[u].pb(v);
        tree[v].pb(u);
    }
    vector<vector<pii>> queries(n + 1);
    vector<int> ans(m, -1);
    FU(i, 0, m - 1) {
        int a, b;
        cin >> a >> b;
        queries[a].pb({b, i});
        queries[b].pb({a, i});
    }
    vector<bool> visited(n + 1, false);
    UnionFind uf(n);
    tarjan(s, -1, tree, queries, visited, uf, ans);
    for (int x : ans) cout << x << endl;
    return 0;
}

2. 倍增在线算法

时间复杂度
• 预处理:O(n log n)(构建每个节点的2^k级祖先表)。
• 单次查询:O(log n)
总体效率:对稀疏查询更灵活。
特点
在线处理:支持实时查询。
• 实现简单,无需复杂数据结构。
• 内存占用较高(存储2^k祖先表)。

示例代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define int long long
#define pb push_back
#define pii pair<int, int>
#define FU(i, a, b) for (int i = (a); i <= (b); ++i)
#define FD(i, a, b) for (int i = (a); i >= (b); --i)
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;

const int MAX_LOG = 20;
vector<vector<int>> tree;
vector<array<int, MAX_LOG>> ancestor;
vector<int> depth;

void dfs(int u, int p) {
    ancestor[u][0] = p;
    for (int v : tree[u]) {
        if (v == p) continue;
        depth[v] = depth[u] + 1;
        dfs(v, u);
    }
}

void build(int n) {
    FU(k, 1, MAX_LOG - 1) {
        FU(u, 1, n) {
            int p = ancestor[u][k - 1];
            ancestor[u][k] = (p == -1) ? -1 : ancestor[p][k - 1];
        }
    }
}

int lca(int u, int v) {
    if (depth[u] < depth[v]) swap(u, v);
    FD(k, MAX_LOG - 1, 0) {
        if (depth[u] - (1 << k) >= depth[v])
            u = ancestor[u][k];
    }
    if (u == v) return u;
    FD(k, MAX_LOG - 1, 0) {
        if (ancestor[u][k] != -1 && ancestor[u][k] != ancestor[v][k]) {
            u = ancestor[u][k];
            v = ancestor[v][k];
        }
    }
    return ancestor[u][0];
}

signed main() {
    cin.tie(0)->ios::sync_with_stdio(0);
    int n, m, s;
    cin >> n >> m >> s;
    tree.resize(n + 1);
    ancestor.resize(n + 1);
    depth.resize(n + 1, 0);
    FU(i, 1, n) ancestor[i].fill(-1);
    FU(i, 1, n - 1) {
        int u, v;
        cin >> u >> v;
        tree[u].pb(v);
        tree[v].pb(u);
    }
    depth[s] = 0;
    dfs(s, -1);
    build(n);
    while (m--) {
        int a, b;
        cin >> a >> b;
        cout << lca(a, b) << endl;
    }
    return 0;
}

关键对比

场景Tarjan离线算法倍增在线算法
查询模式离线(预先已知所有查询)在线(实时处理查询)
时间复杂度(预处理)O(n + Q)O(n log n)
单次查询时间近似O(1)O(log n)
适用数据规模超大规模查询(Q极大)中等规模查询(Q较小)
实现复杂度较高(需并查集优化)较低(仅需预处理祖先表)

结论

Tarjan更快
若所有查询可以预先处理(如静态问题、批量查询),且Q极大(如竞赛题中Qn同阶或更高),Tarjan算法显著更优。
倍增更灵活
若需要实时响应查询或查询动态变化(如交互式场景),倍增法是更优选择。

根据实际需求选择算法:离线批处理用Tarjan,在线查询用倍增。

(本文由deepseek辅助生成)


http://www.kler.cn/a/578961.html

相关文章:

  • Shell编程概述与Shell变量
  • 物联网中设备异构的问题-甚至可以用工业数据采集器?
  • C++之序列容器(vector,list,dueqe)
  • 在运维工作中,Lvs、nginx、haproxy工作原理分别是什么?
  • 【TI】如何更改 CCS20.1.0 的 WORKSPACE 默认路径
  • 开发环境搭建-05.后端环境搭建-前后端联调-通过断点调试熟悉项目代码特点
  • Hot 3D 人体姿态估计 HPE Demo复现过程
  • Excel 粘贴数据到可见单元格
  • 信号的希尔伯特变换与等效基带表示:原理与Matlab实践
  • [数据分享第七弹]全球洪水相关数据集
  • 使用OpenCV和MediaPipe库——实现人体姿态检测
  • 论文阅读方法
  • 2008-2024年中国手机基站数据/中国移动通信基站数据
  • GHCTF2025--Web
  • Windows软件插件-音视频文件读取器
  • 稚晖君级硬核:智元公司开源机器人通信框架AimRT入驻GitCode平台
  • Word2Vec向量化语句的计算原理
  • Guava Cache 中LocalCache的分段锁实现
  • UV,纹理,材质,对象
  • electron的通信方式(三种)