树链剖分学习笔记
怕到时候忘了,来写一篇笔记
前置芝士:树的存储与遍历,
树链剖分的思想及能解决的问题:#
树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。
树链剖分(树剖/链剖)有多种形式,如 重链剖分,长链剖分 和用于 Link/cut Tree 的剖分(有时被称作“实链剖分”),大多数情况下(没有特别说明时),“树链剖分”都指“重链剖分”。
重链剖分可以将树上的任意一条路径划分成不超过
重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。
重链剖分#
我们给出一些定义:
定义 重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
定义:轻子节点表示剩余的所有子结点。
从这个结点到重子节点的边为重边。
到其他轻子节点的边为轻边。
若干条首尾衔接的重边构成重链。
把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
以上来自Oi-wiki。
树链剖分的实现#
以洛谷的模板题为例。
先给出以下定义:
树剖的实现过程分为:
#
代码实现如下:
void dfs1(int u, int fath)
{
fa[u] = fath, dep[u] = dep[fath] + 1;
siz[u] = 1;
for(int i = h[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fath) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
#
代码实现如下:
void dfs2(int u, int fst) //处理top,id,fst表示当前链的顶端
{
id[u] = ++ idx;
a[idx] = w[u]; //a数组表示节点u的权值在线段树中的位置权值
top[u] = fst;
if(!son[u]) return; //没有重儿子意味着没有子节点
dfs2(son[u], fst); //跟重儿子一条链
for(int i = h[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fa[u] || v == son[u]) continue; //轻儿子既不是父节点也不是重儿子
dfs2(v, v); //以自己为链顶重新开一条链
}
}
树剖求解 (最近公共祖先):#
思路:不断的将两个节点中所在链的链顶深度深的节点往上跳,知道两个节点所在链相同,即
代码如下:
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]) swap(u, v);
return u;
}
操作一:将树从 到结点 最短路径上所有节点的值都加上 #
这个时候就要祭出我们的线段树了,在
思路与求解
void path_update(int u, int v, int w)
{
w %= p;
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u, v);
update(id[top[u]], id[u], 1, w);
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
update(id[u], id[v], 1, w);
}
操作二:求树从 到 结点最短路径上所有节点的值之和#
理解了
int path_query(int u, int v)
{
int res = 0;
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u, v);
res = (res + query(id[top[u]], id[u], 1)) % p;
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
res = (res + query(id[u], id[v], 1)) % p;
return res;
}
操作三:将以 为根节点的子树内所有节点值都加上 #
因为我们在对树进行剖分的过程中使用的是
代码如下:
void son_update(int u, int w)
{
w %= p;
update(id[u], id[u] + siz[u] - 1, 1, w);
}
操作四:求以 为根节点的子树内所有节点值之和#
跟操作三基本一样,这里不多赘述,直接放代码:
int son_query(int u)
{
return query(id[u], id[u] + siz[u] - 1, 1) % p;
}
完整代码#
#include <bits/stdc++.h>
#define L(i, j, k) for(int i = (j); i <= (k); i ++)
#define R(i, j, k) for(int i = (j); i >= (k); i --)
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
int n, m, r, p;
int w[N], a[N];
int h[N], nxt[N << 1], to[N << 1], cnt;
int id[N], top[N], fa[N], dep[N], siz[N], son[N], idx;
struct segment_tree
{
int l, r, val, tag;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define val(x) tr[x].val
#define tag(x) tr[x].tag
} tr[N << 2];
void pushup(int x)
{
val(x) = (val(x << 1) + val(x << 1 | 1)) % p;
}
void pushdown(int x)
{
if(!tag(x)) return;
tag(x << 1) = (tag(x) + tag(x << 1)) % p;
tag(x << 1 | 1) = (tag(x) + tag(x << 1 | 1)) % p;
val(x << 1) = (val(x << 1) + (r(x << 1) - l(x << 1) + 1) * tag(x) % p) % p;
val(x << 1 | 1) = (val(x << 1 | 1) + (r(x << 1 | 1) - l(x << 1 | 1) + 1) * tag(x) % p) % p;
tag(x) = 0;
}
void build(int l, int r, int x)
{
l(x) = l, r(x) = r;
if(l == r)
{
val(x) = a[l];
return;
}
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
pushup(x);
}
void update(int l, int r, int x, int v)
{
if(l <= l(x) && r(x) <= r)
{
val(x) = (val(x) + (r(x) - l(x) + 1) * v % p) % p;
tag(x) = (tag(x) + v) % p;
return;
}
pushdown(x);
int mid = l(x) + r(x) >> 1;
if(l <= mid) update(l, r, x << 1, v);
if(r > mid) update(l, r, x << 1 | 1, v);
pushup(x);
}
int query(int l, int r, int x)
{
if(l <= l(x) && r(x) <= r)
return val(x);
pushdown(x);
int mid = l(x) + r(x) >> 1, res = 0;
if(l <= mid) res = (res + query(l, r, x << 1)) % p;
if(r > mid) res = (res + query(l, r, x << 1 | 1)) % p;
return res;
}
void add(int u, int v)
{
to[++ cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
void dfs1(int u, int fath)
{
fa[u] = fath, dep[u] = dep[fath] + 1;
siz[u] = 1;
for(int i = h[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fath) continue;
dfs1(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int fst)
{
top[u] = fst, id[u] = ++ idx, a[idx] = w[u];
if(!son[u]) return;
dfs2(son[u], fst);
for(int i = h[u]; i; i = nxt[i])
{
int v = to[i];
if(v == son[u] || v == fa[u]) continue;
dfs2(v, v);
}
}
void path_update(int u, int v, int w)
{
w %= p;
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u, v);
update(id[top[u]], id[u], 1, w);
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
update(id[u], id[v], 1, w);
}
int path_query(int u, int v)
{
int res = 0;
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u, v);
res = (res + query(id[top[u]], id[u], 1)) % p;
u = fa[top[u]];
}
if(dep[u] > dep[v]) swap(u, v);
res = (res + query(id[u], id[v], 1)) % p;
return res;
}
void son_update(int u, int w)
{
w %= p;
update(id[u], id[u] + siz[u] - 1, 1, w);
}
int son_query(int u)
{
return query(id[u], id[u] + siz[u] - 1, 1) % p;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> r >> p;
L(i, 1, n) cin >> w[i];
L(i, 1, n - 1)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs1(r, 0);
dfs2(r, r);
build(1, n, 1);
while(m --)
{
int opt, x, y, z;
cin >> opt;
if(opt == 1) cin >> x >> y >> z, path_update(x, y, z);
else if(opt == 2) cin >> x >> y, cout << path_query(x, y) << '\n';
else if(opt == 3) cin >> x >> z, son_update(x, z);
else cin >> x, cout << son_query(x) << '\n';
}
return 0;
}
到这里就能很轻松的解决树上修改等一系列问题了,接下来给出几道例题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战