其他分享
首页 > 其他分享> > 树链剖分学习笔记

树链剖分学习笔记

作者:互联网

目录

概述

树链剖分(轻重链剖分)是一个将树上问题转换为序列上问题的算法,能够高效(具体来说,是 \(O(\log^{2}n)\) 的时间复杂度)解决一些树上问题。

基本概念

重儿子:一个节点(当然不是叶子结点)的子节点们中的子节点最多的就是重儿子。

轻儿子:除重儿子以外的子节点就是轻儿子。

重边:链接两个重儿子的边。

轻边:除了重边以外的边。

重链:相连的几个重边所组成的链。

轻链:除重链以外的链。

预处理操作

dfs1

在这个dfs中,需要处理的东西有:

关键代码:

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中,需要处理的东西有:

关键代码如下:

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