树链剖分模版

本文详细介绍了树链剖分的概念及其实现方法,包括重儿子、轻儿子、重边、轻边等概念,并通过两个深度优先搜索实现了top和id的求解。此外,还提供了修改操作、求和操作及求极值操作的具体实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘自http://blog.sina.com.cn/s/blog_6974c8b20100zc61.html
稍有改动

“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。

树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
记size[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的重链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),id[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。

重儿子:size[u]为v的子节点中size值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。

剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则size[u] * 2 < size[v];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。


算法实现:
我们可以用两个dfs来求出fa、dep、size、son、top、id。
dfs1:把fa、dep、size、son求出来,比较简单,略过。
dfs2:
⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记id[son[v]] = idx + 1,idx表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进dfs2(son[v]);
 ⒉对于v的各个轻儿子u,显然有top[u] = u,并且id[u] = idx + 1,进行dfs2过程。
这就求出了top和id。
将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

修改操作:例如将u到v的路径上每条边的权值都加上某值x。
一般人需要先求LCA,然后慢慢修改u、v到公共祖先的边。而高手就不需要了。
记f1 = top[u],f2 = top[v]。
当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新u到f1的父边的权值(logn),并使u = fa[f1]。
当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
重复上述过程,直到修改完成。

求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。

如下图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。

这里写图片描述

当要修改11到10的路径时。
第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
第二次迭代:dep[f1] > dep[f2],修改线段树中9--10--11号点。u = 1, f1 = 1
第三次迭代:f1 == f2,退出循环,并判断dep[u]跟dep[v]的深度,因为dep[u] < dep[v],所以更新线段树中的1号结点

两次DFS

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 50005;

int dep[N], f[N], son[N], size[N], top[N], id[N], bit[N], val[N], idx;
int n, m, p;
vector<int> g[N];

//dep存储的是节点的深度
//size记录的是以u为根结点的子树的节点数
//f数组记录的是每个节点的父节点
//son记录的是与u同在一重链上的u的子节点
//初始值1, -1, 1

void dfs1(int u, int fa, int depth) {
    dep[u] = depth;
    size[u] = 1;
    f[u] = fa;
    son[u] = 0;
    for (int i = 0; i < g[u].size(); i++) {
        int v = g[u][i];
        if (v == fa) continue;
        dfs1(v, u, depth + 1);
        size[u] += size[v];
        if (size[son[u]] < size[v]) son[u] = v;
    }
}

//top记录的是结点所在的链的顶端结点
//id记录的是树的结点对应的的值
//初始值是1,1

void dfs2(int u, int tp) {
    id[u] = ++idx;
    top[u] = tp;
    //son[u]是重链
    if (son[u]) dfs2(son[u], tp);
    for (int i = 0; i < g[u].size(); i++) {
        int v = g[u][i];
        if (v == f[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

点权的询问或者修改,这里使用的是树状数组,线段树同理

void add(int x, int v) {
    while (x < N) {
        bit[x] += v;
        x += lowbit(x);
    }
}

void add(int l, int r, int v) {
    add(l, v);
    add(r + 1, -v);
}

void gao(int u, int v, int w) {
    int tp1 = top[u], tp2 = top[v];
    while (tp1 != tp2) {
        if (dep[tp1] < dep[tp2]) {
            swap(tp1, tp2);
            swap(u, v);
        }
        add(id[tp1], id[u], w);
        u = f[tp1];
        tp1 = top[u];
    }
    if (dep[u] > dep[v]) swap(u, v);
    add(id[u], id[v], w);
}

附上模版题:HDU-3966 (点权,可用树状数组维护)
HYSBZ - 1036 树的统计Count (点权)
FZU - 2082 过路费(边权)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值