树链剖分学习笔记
作者:互联网
概述
树链剖分(轻重链剖分)是一个将树上问题转换为序列上问题的算法,能够高效(具体来说,是 \(O(\log^{2}n)\) 的时间复杂度)解决一些树上问题。
基本概念
重儿子:一个节点(当然不是叶子结点)的子节点们中的子节点最多的就是重儿子。
轻儿子:除重儿子以外的子节点就是轻儿子。
重边:链接两个重儿子的边。
轻边:除了重边以外的边。
重链:相连的几个重边所组成的链。
轻链:除重链以外的链。
预处理操作
dfs1
在这个dfs中,需要处理的东西有:
- 每个点的深度 \(\texttt{dep[i]}\)。
- 每个点的父亲节点 \(\texttt{fa[i]}\)。
- 每个非叶子节点的子树大小(包括自己) \(\texttt{siz[i]}\)。
- 每个非叶子节点的重儿子编号 \(\texttt{son[i]}\)。
关键代码:
void dfs1(int u, int father, int deep) {
dep[u] = deep;
siz[u] = 1;
fa[u] = father;
for (int i = head[u]; i; i = g[i].nxt) {
int v = g[i].to;
if (v == father) {
continue;
}
dfs1(v, u, deep + 1);
siz[u] += siz[u];
if (siz[v] >= siz[son[u]]) {
son[u] = v;
}
}
}
dfs2
在这个dfs中,需要处理的东西有:
- 标记每个点在线段树(或其他数据结构)中的标号 \(\texttt{sgt[i]}\)。
- 标记 \(\texttt{sgt[i]}\) 的反操作 \(\texttt{rev[i]}\)。
- 标记每一个点中链的顶端 \(\texttt{top[i]}\)。
- 处理每一条链。
- 注意优先处理重儿子,然后处理轻儿子。
关键代码如下:
void dfs2(int u,int fa){
if(son[u]){
seg[son[u]]=++seg[0];
top[son[u]]=top[u];
rev[seg[0]]=son[u];
dfs2(son[u],u);
}
for(int i=head[u];i;i=g[i].nxt){
int v=g[i].to;
if(top[v])continue;
seg[v]=++seg[0];
rev[seg[0]]=v;
top[v]=v;
dfs2(v,u);
}
}
具体处理问题
在路径上的操作,我们需要一条条链往上跳。
1.LCA问题
P3379 【模板】最近公共祖先(LCA)
如题,给定一棵有根 \(s\),节点数为 \(N\) 的多叉树,有 \(M\) 个询问。对于每个询问,请求出指定两个点 \(a,b\) 直接最近的公共祖先。
对于 \(100\%\) 的数据,\(N\leq 500000\),\(M\leq 500000\)。
对于这种问题,可以使用倍增、Tarjan或者RMQ,当然也可以树剖。
我们一条链一条链往上跳,跳到一起就是LCA了。
关键代码如下:
int lca(int x, int y) {
int fx = top[x], fy = top[y];
while (fx != fy) {
if (dep[fx] < dep[fy]){
swap(fx, fy);
swap(x, y);
}
x = fa[fx], fx = top[x];
}
if (dep[x] > dep[y]) {
return y;
}
else return x;
}
Game2:简单树上问题
给你一个 \(N\) 顶点的树,有 \(M\) 个询问,每次询问给出两个节点 \(u,v\) 求这两个点之间的最短距离。
\(1 \leq N,M \leq 100000\)
时间限制 \(50\operatorname{ms}\),空间限制 \(50\operatorname{MB}\)。
其实两个点的距离就是它们的深度和减去两倍的LCA的深度。
本题倍增过不去,只能用树剖。
关键代码如下:
while(m--){
int a,b;
cin>>a>>b;
cout<<dep[a]+dep[b]-2*dep[lca(a,b)]<<'\n';
}
P4281 [AHOI2008] 紧急集合 / 聚会
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有 \(n\) 个等待点,有 \(n-1\) 条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。
参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在 \(n\) 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。
小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?游戏共有 \(m\) 次。
对于 \(100\%\) 的数据,\(1\leq n \leq 5\times10^5\),\(1\leq m\leq 5\times 10^5\)。
其实就是在求三点间的最短距离。
关键代码如下:
inline int cd(int a,int b,int LCA){
return dep[a]+dep[b]-2*dep[LCA];
}
while(m--){
int x,y,z;
cin>>x>>y>>z;
int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),ll=0;
if(l1==l2)ll=l3;
else if(l1==l3)ll=l2;
else ll=l1;
cout<<ll<<' ';
cout<<(dep[x]+dep[y]+dep[z]-dep[l1]-dep[l2]-dep[l3])<<'\n';
}
P5903 【模板】树上 k 级祖先
给定一棵 \(n\) 个点的有根树。
有 \(q\) 次询问,第 \(i\) 次询问给定 \(x_i, k_i\),要求点 \(x_i\) 的 \(k_i\) 级祖先,答案为 \(ans_i\)。特别地,\(ans_0 = 0\)。
本题中的询问将在程序内生成。
给定一个随机种子 \(s\) 和一个随机函数 \(\operatorname{get}(x)\):
#define ui unsigned int
ui s;
inline ui get(ui x) {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return s = x;
}
你需要按顺序依次生成询问。
设 \(d_i\) 为点 \(i\) 的深度,其中根的深度为 \(1\)。
对于第 \(i\) 次询问,\(x_i = ((\operatorname{get}(s) \operatorname{xor} ans_{i-1}) \bmod n) + 1\),\(k_i = (\operatorname{get}(s) \operatorname{xor} ans_{i-1}) \bmod d_{x_i}\)。
对于 \(100\%\) 的数据,\(2 \le n \le 5 \times 10^5\),\(1 \le q \le 5 \times 10^6\),\(1 \le s < 2^{32}\)。
本题树剖可过,以后写,先咕咕了。
2.单点修改,链上求值
P2590 [ZJOI2008]树的统计
一棵树上有 \(n\) 个节点,编号分别为 \(1\) 到 \(n\),每个节点都有一个权值 \(w\)。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t
: 把结点 \(u\) 的权值改为 \(t\)。
II. QMAX u v
: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的最大权值。
III. QSUM u v
: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的权值和。
注意:从点 \(u\) 到点 \(v\) 的路径上的节点包括 \(u\) 和 \(v\) 本身。
对于 \(100 \%\) 的数据,保证 \(1\le n \le 3\times 10^4\),\(0\le q\le 2\times 10^5\)。
中途操作中保证每个节点的权值 \(w\) 在 \(-3\times 10^4\) 到 \(3\times 10^4\) 之间。
这道题就需要 \(\texttt{sgt[i],rev[i]}\) 了。
对于 \(\texttt{CHANGE}\) 操作,我们直接修改 \(\texttt{seg[u]}\) 即可。
对于询问,我们可以在链上跳,每跳一次我们都进行一次查询,最后汇总。
贴上完整代码:
#include <bits/stdc++.h>
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
#define DEBUG cerr<<"Hello World"
#define int long long
using namespace std;
struct edge{
int nxt,to,w;
} g[30005*2];
int head[30005],ec;
void add(int from,int to,int weight){
g[++ec].nxt=head[from];
g[ec].to=to;
g[ec].w=weight;
head[from]=ec;
}
namespace sgt {
int maxt[30005<<2],sumt[30005<<2];
inline void pushup(int i) {
maxt[i]=max(maxt[ls],maxt[rs]);
sumt[i]=sumt[ls]+sumt[rs];
}
void update(int p,int v,int i,int l,int r) {
if(p>r||p<l)return;
if(l==r) {
maxt[i]=sumt[i]=v;
return;
}
if(p<=mid) {
update(p,v,ls,l,mid);
}
if(p>mid) {
update(p,v,rs,mid+1,r);
}
pushup(i);
}
int qmax(int ql,int qr,int i,int l,int r) {
if(ql<=l&&r<=qr) {
return maxt[i];
}
int result = LLONG_MIN;
if(ql<=mid) {
result=max(result,qmax(ql,qr,ls,l,mid));
}
if(qr>mid) {
result = max(result,qmax(ql,qr,rs,mid+1,r));
}
return result;
}
int qsum(int ql,int qr,int i,int l,int r) {
if(ql<=l&&r<=qr) {
return sumt[i];
}
int result = 0;
if(ql<=mid) {
result = (result+qsum(ql,qr,ls,l,mid));
}
if(qr>mid) {
result = (result+qsum(ql,qr,rs,mid+1,r));
}
return result;
}
};
using namespace sgt;
int siz[30005],father[30005],dep[30005],son[30005];
int top[30005],seg[30005],rev[30005];
void dfs1(int u,int fa) {
siz[u]=1;
father[u]=fa;
dep[u]=dep[fa]+1;
for(int i=head[u];i;i=g[i].nxt){
int v=g[i].to;
if(v==fa)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]){
son[u]=v;
}
}
}
void dfs2(int u,int fa){
if(son[u]){
seg[son[u]]=++seg[0];
top[son[u]]=top[u];
rev[seg[0]]=son[u];
dfs2(son[u],u);
}
for(int i=head[u];i;i=g[i].nxt){
int v=g[i].to;
if(top[v])continue;
seg[v]=++seg[0];
rev[seg[0]]=v;
top[v]=v;
dfs2(v,u);
}
}
int num[30005];
void build(int i,int l,int r){
if(l==r){
maxt[i]=sumt[i]=num[rev[l]];
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(i);
}
pair<int,int> query(int x,int y){
int ans1=0,ans2=LLONG_MIN;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]){
swap(x,y);
}
ans1+=qsum(seg[top[x]],seg[x],1,1,seg[0]);
ans2=max(ans2,qmax(seg[top[x]],seg[x],1,1,seg[0]));
x=father[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
ans1+=qsum(seg[x],seg[y],1,1,seg[0]);
ans2=max(ans2,qmax(seg[x],seg[y],1,1,seg[0]));
return make_pair(ans1,ans2);
}
int n,m;
signed main() {
memset(maxt,0x8f,sizeof(maxt));
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
add(u,v,114);
add(v,u,514);
}
for(int i=1;i<=n;i++){
cin>>num[i];
}
seg[0]=seg[1]=top[1]=rev[1]=1;
dfs1(1,0),dfs2(1,0);
build(1,1,seg[0]);
cin>>m;
while(m--){
string s;
int u,v;
cin>>s>>u>>v;
if(s[0]=='C'){
update(seg[u],v,1,1,seg[0]);
}
else if(s[1]=='M'){
cout<<query(u,v).second<<'\n';
}
else{
cout<<query(u,v).first<<'\n';
}
}
return 0;
}
168行,真吉利。
标签:剖分,int,siz,top,笔记,树链,seg,dep,son 来源: https://www.cnblogs.com/zheyuanxie/p/hld.html