ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树链剖分学习笔记

2022-07-17 16:32:12  阅读:133  来源: 互联网

标签:剖分 int siz top 笔记 树链 seg dep son


目录

概述

树链剖分(轻重链剖分)是一个将树上问题转换为序列上问题的算法,能够高效(具体来说,是 \(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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有