树链剖分

前言

今天szt学长给我们讲了树剖,听完之后脑子着实嗡嗡的,这什么玩意啊
,本来想做题,但还是想先巩固一下吧。于是这篇文章诞生了…

树剖能干什么?

  • 修改两点路径上的值
  • 查询两点路径上的值
  • 修改单点的值
  • 查询单点的值

前置知识

1.线段树,不会请退役。

2.dfs序,不会请自行百度,这里给篇文章

前置概念

  • 1.重儿子:每个节点都会有若干个儿子节点(除叶节点),而只要这个节点不是叶子节点,它都是一棵子树的根。那么,这个父亲节点就有很多个儿子,而一定会有一个儿子的儿子最多,也就是子树最大。那么这个儿子节点就叫做\(x\)的重儿子。

  • 2.轻儿子:与除重儿子外的儿子节点都是轻儿子。

  • 3.重边:父节点与重儿子之间的边。

  • 4.轻边:与重边相对。

  • 5.重链:重边连起来的链。

  • 6.轻链:轻边连起来的链。

树链剖分的预处理

这里需要两个dfs

dfs1:

预处理出每个节点的父亲,子树的大小,节点的深度以及重儿子是谁。

代码如下

void dfs1(int u,int Fa) {
	Size[u]=1;fa[u]=Fa;dep[u]=dep[Fa]+1;
	for(int i=head[u]; i; i=e[i].nxt) {
		int v=e[i].v;if(v==Fa) continue;
		dfs1(v,u); Size[u]+=Size[v];
		if(Size[son[u]]<Size[v]) son[u]=v;
		//如果该节点的子树大小比当前记录的重儿子的子树大小大,更新
	}
}

dfs2

void dfs2(int u,int Top) {
	top[u]=Top;dfn[u]=++tot;pre[tot]=u;
	//top记录该链的链顶是哪个节点
	//pre记录dfs序 
	//dfn记录节点在dfs序中的位置 
	if(son[u]) dfs2(son[u],Top);
	//如果有重儿子先遍历重儿子所在的链。
	for(int i=head[u]; i; i=e[i].nxt) {
		int v=e[i].v;
		if(v==fa[u] or son[u]==v) continue;
		dfs2(v,v);
		//非父非重儿子,即为轻链,节点和链顶都是自己。
	}
	return; 
}

有点糊,在经szt的确认下,可以这么理解,只把重链当成一条链,轻链仅仅包含其所在的一条边。

树链剖分的操作

通过刚才的预处理,我们已经把树上节点的新编号变成了连续的。然后我们就可以针对区间(链)把原树变成线段树来进行我们要做的一系列操作。

一份线段树代码(模板题上粘的有取模请自行无视):

namespace segment{
    struct node{
    	int len,lazy,sum;
	}tree[N<<2];
	#define lson pos<<1
	#define rson pos<<1|1
	void build(int pos,int l,int r){
		tree[pos].len=(r-l+1);
		if(l==r){
			tree[pos].sum=a[pre[l]]%p;
			return;
		}
		int mid=l+r>>1;
		build(lson,l,mid);build(rson,mid+1,r);
		tree[pos].sum=(tree[lson].sum+tree[rson].sum)%p;
		return;
	}
	
	void push_down(int pos){
		if(tree[pos].lazy){
			tree[lson].lazy=(tree[pos].lazy+tree[lson].lazy)%p;
			tree[rson].lazy=(tree[pos].lazy+tree[rson].lazy)%p;
			tree[lson].sum=(tree[lson].sum+tree[pos].lazy*tree[lson].len)%p;
			tree[rson].sum=(tree[rson].sum+tree[pos].lazy*tree[rson].len)%p;
			tree[pos].lazy=0;
		}
	    return;
	}
	
	void change(int pos,int l,int r,int L,int R,int k){
		if(l>=L&&r<=R){
			tree[pos].sum=(tree[pos].sum+tree[pos].len*k)%p;
			tree[pos].lazy+=k;
			return;
		} if(l>R or r< L) return;
		push_down(pos);int mid=l+r>>1;
		if(L<=mid) change(lson,l,mid,L,R,k);
		if(R>mid) change(rson,mid+1,r,L,R,k);
		tree[pos].sum=(tree[lson].sum+tree[rson].sum)%p;
	    return;
	}
	
	int query(int pos,int l,int r,int L,int R){
		if(l>=L&&r<=R) return tree[pos].sum%p;
		push_down(pos);int mid=l+r>>1,res=0;
		if(L<=mid) res=(res+query(lson,l,mid,L,R)%p)%p;
		if(R>mid) res=(res+query(rson,mid+1,r,L,R)%p)%p;
		return res%p;
	}
} 

唯一的不同就在 \(build\) 当中的 $ tree[pos].sum=a[pre[l]]$ 了,意思就是pos这个点对应dfs序的点的值。

好像很绕,给个szt的图感受一下?

树链剖分

树链剖分

貌似可以理解。

上一篇:lazy加载


下一篇:Kotlin开发中的一些Tips,自学Android