ICode9

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

树链剖分の学习笔记

2021-07-26 12:03:28  阅读:161  来源: 互联网

标签:线段 剖分 dep top 笔记 树链 int dfn 节点


一.树链剖分的概念

树链剖分是一种对树进行划分的算法,将树划分为若干个链,以便维护树上路径的信息。

也就是说,我们将树划分为若干个线性结构,使用一些数据结构来维护它,如:树状数组,线段树,splay等。


二.树链剖分的作用

前面我们提到,我们将树划分为多个线性结构,用数据结构来维护它,所以我们可以很快捷地对于树上的某些信息进行修改或者查询等操作,例如:修改某条树上路径的所有点权;查询某条树上路径点权的极值、和、等。


三.重链剖分

我们先要了解一些很重要的定义。

重子节点:其子节点中子树大小最大的节点,若有多个,任取其一。若没有子节点,那么久没有重子节点。

轻子节点:其子节点中除重子节点以外的所有子节点。

重边:从当前节点到重子节点的边。

轻边:从当前节点到轻子节点的边。

重链:若干条相连接的重边

这里给出一张摘自oi-wiki的图片供读者理解。


四.应用

我们先给出一些变量名的定义:

top[i]//表示节点i所在的重链的顶部节点。
dfn[i]//表示节点i的dfs序,也是节点i在线段树中的序号。
dep[i]//表示节点i的深度。
fa[i]//表示节点i的父亲。
siz[i]//表示节点i的子树的节点个数。
son[i]//表示节点i的重子节点。
sgt[i]//表示dfs序所对应的节点编号。

求两个节点 \(x\) 和 \(y\) 的路径上的点权之和:

int query_tree (int x, int y) {
	int ans = 0;
	
	while (top[x] != top[y]) {
   	//x和y不在同一条重链上。
		if (dep[top[x]] < dep[top[y]]) {
			swap (x, y);
		} 
		
		ans += query (1, dfn[top[x]], dfn[x]);
        //用线段树维护链上信息。
		
		x = fa[top[x]];
        //将x设为原重链链头的父节点,继续从轻边循环。
	}
	
	if (dep[x] > dep[y]) {
		swap (x, y);
	}
    //x和y已经在同一条重链上了。
	
	ans += query (1, dfn[x], dfn[y]);
    //用线段树维护链上信息。
	
	return ans;
}

这个操作就很像 \(LCA\) ,我们使用了 \(top\) 进行加速。

注意,我们每次循环的时候只能让同一个节点向上跳,否则会出现少计算答案的情况。

同理,修改树上某条路径的点权也是一样的。


求以节点 \(x\) 为根节点的子树内所有点权和:

我们知道,我们将树划分为了多个线性结构,也就是链。

于是,我们就可以利用一些数据结构进行维护。

所以,我们在查询子树和的时候,只需要查询一段区间的和。

这里我用线段树进行查询,代码如下:

query (1, dfn[x], dfn[x] + siz[x] - 1);

五.例题

P3384 【模板】轻重链剖分/树链剖分

这道题就是树链剖分的模板题。

对于 \(3\) , \(4\) 操作,我们可以将其化为线性结构进行维护。

而 \(1\) ,\(2\) 操作,我们只需要进行树上操作即可。

操作 \(1\) :

void addtree (int x, int y, int k) {
	while (top[x] != top[y]) {
	//不在同一条重链上。
		if (dep[top[x]] < dep[top[y]]) {
			swap (x, y);
		}
		
		addS (1, dfn[top[x]], dfn[x], k);
        //线段树区间修改。
		
		x = fa[top[x]];
        //向上跳转。
	}
	
	if (dep[x] > dep[y]) {
		swap (x, y);
	}
    //现在节点x和y在同一条重链上。
	
	addS (1, dfn[x], dfn[y], k);
    //线段树区间修改。
}

操作 \(2\) :

int query_tree (int x, int y) {
	int ans = 0;
	
	while (top[x] != top[y]) {
	//不在同一条重链上。
		if (dep[top[x]] < dep[top[y]]) {
			swap (x, y);
		} 
		
		ans += query (1, dfn[top[x]], dfn[x]);
        //线段树区间查询。            
		ans %= p;
		
		x = fa[top[x]];
        //向上跳转。
	}
	
	if (dep[x] > dep[y]) {
		swap (x, y);
	}
    
    //节点x和y在同一条重链上的时候。
	
	ans += query (1, dfn[x], dfn[y]);
	//线段树区间查询。
    
	return ans %p;
}

操作 \(3\):

addS (1, dfn[x], dfn[x] + siz[x] - 1, y % p);
//运用线段树的区间修改。

操作 \(4\):

printf ("%d\n", query (1, dfn[x], dfn[x] + siz[x] - 1) %p);
//运用线段树的区间查询。

完整代码。


P3178 [HAOI2015]树上操作

标签:线段,剖分,dep,top,笔记,树链,int,dfn,节点
来源: https://www.cnblogs.com/chenyuhe/p/15060796.html

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

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

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

ICode9版权所有