P3379 【模板】最近公共祖先(LCA,树链剖分)

本文详细介绍了一种求解最近公共祖先(LCA)问题的有效算法,并提供了完整的C++代码实现。通过预处理重链剖分,该算法能在O(log N)的时间复杂度内找到任意两点的最近公共祖先。

传送门

核心函数

int lca(int u, int v)
{
    while (top[u] != top[v])//如果u,v在不同的重链,向上跳
    {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        u = fa[top[u]];
    }
    //u,v在同一条链上了(重链没有分叉&&此时的u,v不是最开始的u,v了)
    if (dep[u] < dep[v])
        return u;
    else
        return v;
}

代码

#include <bits/stdc++.h>

using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)

//---------------
const int N = 5e5 + 10;
int n, m, root;
int h[N], e[N << 1], ne[N << 1], idx;
int top[N], fa[N], sz[N], son[N], dep[N];
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
void dfs1(int u, int father, int depth)
{
    dep[u] = depth, fa[u] = father, sz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int t = e[i];
        if (t == father)
            continue;
        dfs1(t, u, depth + 1);
        sz[u] += sz[t];
        if (son[u] == 0 || sz[son[u]] < sz[t])
        {
            son[u] = t;
        }
    }
}
void dfs2(int u, int v)
{
    top[u] = v;
    if (son[u] == 0)
        return;
    dfs2(son[u], v);
    for (int i = h[u]; ~i; i = ne[i])
    {
        int t = e[i];
        if(t==fa[u]||t==son[u]) continue;
        dfs2(t,t);
    }
}

int lca(int u, int v)
{
    while (top[u] != top[v])
    {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        u = fa[top[u]];
    }
    
    if (dep[u] < dep[v])
        return u;
    else
        return v;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int StartTime = clock();
#endif
    scanf("%d%d%d", &n, &m, &root);
    init_h;
    fir(i, 1, n - 1)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        add(b, a);
    }
    dfs1(root, -1, 1);
    dfs2(root,root);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
#ifndef ONLINE_JUDGE
    printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
    return 0;
}
<think> 嗯,用户想了解树链剖分的应用场景,这需要结合树链剖分的核心特性来分析。用户提供的引用材料已经涵盖了关键点:重链剖分技术、DFS序连续性、路径拆分复杂度O(logn)等特性。这些特性决定了树链剖分特别适合处理树上的路径问题。 从引用[^3]可以看出,树链剖分能将任意路径拆分为不超过logn条连续链,且DFS序连续——这个特性直接关联到数据结构的高效维护。引用[^4]的代码示例则展示了对路径求和的应用,这种基于跳转链的查询方式非常典型。 树链剖分的应用场景应该聚焦在需要高效处理树上路径操作的场景。比如修改或查询节点路径上的信息,或者子树信息。当问题涉及这两类操作时,树链剖分配合线段树等数据结构就能发挥巨大优势。 具体应用场景包括:1)路径修改查询,比如路径节点值求和或最值查询;2)子树整体操作,因为DFS序连续,子树对应区间操作;3)LCA相关操作,在剖分过程中自然得到;4)动态树问题,比如Link-Cut Tree的基础;5)子树统计类问题,比如子树节点数量统计。 用户提供的引用[^1][^2]也提到重链首尾相接形成连续区间,这解释了为什么能套用线段树。而引用[^4]的代码实例正好验证了路径查询的实现模式。这些特性共同支撑了上述应用场景的实现基础。 用户可能还想知道这些场景的实际案例,比如算法竞赛中修改树上路径权值的问题(如洛谷P3384模板题),或者网络管理中动态更新节点状态的应用。不过当前问题聚焦在应用场景分类,所以先列举核心场景更合适。 </think> 树链剖分(树剖)的核心价值在于**将树形结构转化为线性序列**(通过DFS序),并利用**重链剖分**将树上路径划分为 $O(\log n)$ 条连续的链。这使得许多复杂的树上路径问题可以借助**线段树、树状数组等区间数据结构**高效解决。其主要应用场景如下: 1. **树上路径的查询与修改(核心应用)**: * **路径节点值求和/最大值/最小值等聚合操作**:例如查询节点 $u$ 到 $v$ 路径上所有点权的和、最大值或最小值[^1][^3][^4]。 * **路径节点值批量修改**:例如将节点 $u$ 到 $v$ 路径上所有点的权值增加一个常数 $k$,或乘以一个常数等[^1][^4]。 * **路径上满足特定条件的节点查询**:结合数据结构进行筛选。 2. **子树查询与修改**: * 由于树剖后的DFS序保证:**一个节点的子树在其DFS序中对应一段连续的区间**。 * **子树节点值求和/最大值/最小值等聚合操作**:例如查询以节点 $u$ 为根的子树中所有点的权值和[^1]。 * **子树节点值批量修改**:例如将以节点 $u$ 为根的子树中所有点的权值增加一个常数 $k$[^1][^4]。 3. **高效求最近公共祖先LCA)**: * 树链剖分过程本身(不断跳 $top$)就是一种高效的求LCA的方法,时间复杂度 $O(\log n)$[^1][^3]。虽然常与数据结构操作结合使用,但其求LCA本身也是重要应用。 4. **动态树问题(Link-Cut Tree, LCT)的基础**: * 树链剖分的概念和思想(特别是重链剖分)是理解和使用更高级的动态树数据结构(如LCT)的基础。LCT用于维护森林的连通性,支持更动态的边连接/断开操作,其核心操作“Preferred Path”可以看作是对重链概念的动态维护[^3]。 5. **子树大小等统计信息维护**: * 在第一次DFS(求重儿子)时,通常会计算每个节点的子树大小 $size[u]$。这个信息本身就常用于各种树形DP或统计问题[^2]。 **总结关键点**: * **把树上复杂的路径操作分解为 $O(\log n)$ 次区间操作**(得益于重链的性质)[^1][^2][^3]。 * **把子树操作转化为单个区间操作**(得益于DFS序的连续性)[^1]。 * **利用区间数据结构(如线段树)高效处理这些操作**[^1][^4]。 **典型问题场景**: * **算法竞赛题目**:大量树形结构题目需要对路径或子树进行修改和查询,树剖是标准解决方案之一(如模板题P3384)[^4]。 * **网络路由与管理**:模拟网络拓扑结构,需要高效查询/修改节点间路径状态或子树状态。 * **文件系统操作**:处理目录树结构上的批量修改或查询(如计算某个目录下所有文件的总大小、修改某个目录下所有文件的权限)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值