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

树链剖分学习笔记

时间:2022-12-10 23:01:15浏览次数:53  
标签:ver 剖分 int void dfs 树链 add 笔记 son

树链剖分学习笔记

简介

树链剖分是一种可以把树丢到线段树上维护的一种算法,时间复杂度为 \(O(n \log^2 n)\)。

思路

一、一些概念

1.重儿子:如果一个点有儿子,那么所有儿子中儿子最多的一个儿子就是这个点的重儿子。有点绕,可以看图理解。图中标红的点就是重儿子。

2.轻儿子:不是重儿子的点就是轻儿子。上图中白色的点就是轻儿子。

3.重链:由一个轻儿子开始,其他所有点都是重儿子的链。下图中红色的链就是重链。

4.dfs序:从根开始,先dfs重儿子再dfs其他儿子,第几个被dfs到就是它的dfs序,下图中点旁边的数字就是dfs序。

二、性质

有了这四个概念,再结合上面那张图,就可以发现一些有趣的性质

1.每一条重链上的dfs序是连续的。

2.每一棵子树里的dfs序也是连续的。

有了这些性质就可以把这棵树的dfs序扔到线段树里去了。

三、实现

1.求出重儿子,子树大小,父亲,深度(dfs1)

void dfs1(int x,int f){//de:深度,siz:子树大小,fa:父亲
	fa[x]=f,de[x]=de[fa[x]]+1,siz[x]=1;
	for(int i=head[x];i;i=nxt[i]){
		if(ver[i]==fa[x])continue;
		dfs1(ver[i],x);
		siz[x]+=siz[ver[i]];
		if(siz[ver[i]]>siz[son[x]])son[x]=ver[i];
	}
}

代码比较简单。

2.求出重链的起点和dfs序(dfs2)

void dfs2(int x,int f){//top:这个节点所在的重链的起点,dfn:dfs序,fdfn:dfs序对应的数
	top[x]=f,dfn[x]=++cnt,fdfn[cnt]=x;
	if(son[x]==0)return;
	dfs2(son[x],f);
	for(int i=head[x];i;i=nxt[i]){
		if(ver[i]==fa[x])continue;
		if(ver[i]==son[x])continue;
		dfs2(ver[i],ver[i]);
	}
}

代码也比较简单。

这样就可以把树放到线段树里维护啦!

例题

洛谷模板

这道题需要的操作是

  • 1 x y z,表示将树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值都加上 \(z\)。

  • 2 x y,表示求树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)。

  • 4 x 表示求以 \(x\) 为根节点的子树内所有节点值之和

操作3和4可以通过性质2和线段树直接解决。

代码:

void x_subtree_add_z(int x,int z){
	tree.change(1,dfn[x],dfn[x]+siz[x]-1,z);
}
int ask_x_subtree(int x){
	return tree.ask(1,dfn[x],dfn[x]+siz[x]-1);
}

操作1和2:

树上两点的最短路径是:x->\(\text{lca}\)(x,y)->y。

我们可以用类似于跳lca的方法做,从一个点开始跳,每一次跳跳到这个重链的起点的父亲,直到两点在同一重链上,然后用线段树维护。

代码:

void x_to_y_add_z(int x,int y,int z){
	while(top[x]!=top[y]){
		if(de[top[x]]<de[top[y]])swap(x,y);//让更深一点的跳
		tree.change(1,dfn[top[x]],dfn[x],z);//先修改(x~top[x])
		x=fa[top[x]];//跳
	}
	if(de[x]<de[y])swap(x,y);//已经在同一条链上
	tree.change(1,dfn[y],dfn[x],z);//修改
}
int ask_x_to_y(int x,int y){//同理
	int ans=0;
	while(top[x]!=top[y]){
		if(de[top[x]]<de[top[y]])swap(x,y);
		ans=(ans+tree.ask(1,dfn[top[x]],dfn[x]));
		x=fa[top[x]];
	}
	if(de[x]<de[y])swap(x,y);
	ans=(ans+tree.ask(1,dfn[y],dfn[x]));
	return ans;
}

完整代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=100005;
int MOD;
//------------------------------------------------------------以下为线段树模板
class SegmentTree{
	private:
		struct node{
			int l,r,data,add;
		}t[MAXN<<2];
		#define LSON (p<<1)
		#define RSON ((p<<1)|1)
		#define mid ((l+r)>>1)
		void make_lazy_tag(int p,int val){
			t[p].data=(t[p].data+(t[p].r-t[p].l+1)*val%MOD)%MOD;
			t[p].add=(t[p].add+val)%MOD;
		}
		void push_up(int p){
			t[p].data=(t[LSON].data+t[RSON].data)%MOD;
		}
		void push_down(int p){
			if(!t[p].add)return;
			make_lazy_tag(LSON,t[p].add);
			make_lazy_tag(RSON,t[p].add);
			push_up(p);
			t[p].add=0;
		}
	public:
		void build(int p,int l,int r,int* a,int* b){
			t[p].l=l,t[p].r=r;
			if(l==r){
				t[p].data=a[b[l]]%MOD;
				return;
			}
			build(LSON,l,mid,a,b);
			build(RSON,mid+1,r,a,b);
			push_up(p);
		}
		void change(int p,int l,int r,int val){
			if(l<=t[p].l&&t[p].r<=r){
				make_lazy_tag(p,val);
				return;
			}
			push_down(p);
			if(t[LSON].r>=l)change(LSON,l,r,val);
			if(t[RSON].l<=r)change(RSON,l,r,val);
			push_up(p);
		}
		int ask(int p,int l,int r){
			if(l<=t[p].l&&t[p].r<=r)return t[p].data%MOD;
			int ans=0;
			push_down(p);
			if(t[LSON].r>=l)ans=(ans+ask(LSON,l,r))%MOD;
			if(t[RSON].l<=r)ans=(ans+ask(RSON,l,r))%MOD;
			return ans%MOD;
		}
}tree;
//------------------------------------------------------------以上为线段树模板
int ver[MAXN<<1],nxt[MAXN<<1],head[MAXN],tot=1,n,m,r,a[MAXN];
int de[MAXN],fa[MAXN],son[MAXN],siz[MAXN],top[MAXN],dfn[MAXN],fdfn[MAXN],cnt;
void add(int x,int y){
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs1(int x,int f){
	fa[x]=f,de[x]=de[fa[x]]+1,siz[x]=1;
	for(int i=head[x];i;i=nxt[i]){
		if(ver[i]==fa[x])continue;
		dfs1(ver[i],x);
		siz[x]+=siz[ver[i]];
		if(siz[ver[i]]>siz[son[x]])son[x]=ver[i];
	}
}
void dfs2(int x,int f){
	top[x]=f,dfn[x]=++cnt,fdfn[cnt]=x;
	if(son[x]==0)return;
	dfs2(son[x],f);
	for(int i=head[x];i;i=nxt[i]){
		if(ver[i]==fa[x])continue;
		if(ver[i]==son[x])continue;
		dfs2(ver[i],ver[i]);
	}
}
void x_to_y_add_z(int x,int y,int z){
	while(top[x]!=top[y]){
		if(de[top[x]]<de[top[y]])swap(x,y);
		tree.change(1,dfn[top[x]],dfn[x],z);
		x=fa[top[x]];
	}
	if(de[x]<de[y])swap(x,y);
	tree.change(1,dfn[y],dfn[x],z);
}
int ask_x_to_y(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(de[top[x]]<de[top[y]])swap(x,y);
		ans=(ans+tree.ask(1,dfn[top[x]],dfn[x]))%MOD;
		x=fa[top[x]];
	}
	if(de[x]<de[y])swap(x,y);
	ans=(ans+tree.ask(1,dfn[y],dfn[x]))%MOD;
	return ans;
}
void x_subtree_add_z(int x,int z){
	tree.change(1,dfn[x],dfn[x]+siz[x]-1,z);
}
int ask_x_subtree(int x){
	return tree.ask(1,dfn[x],dfn[x]+siz[x]-1)%MOD;
}
signed main(){
	scanf("%lld%lld%lld%lld",&n,&m,&r,&MOD);
	for(int i=1,x;i<=n;i++){
		scanf("%lld",&x);
		a[i]=x%MOD;
	}
	for(int i=1,f,t;i<n;i++){
		scanf("%lld%lld",&f,&t);
		add(f,t);
		add(t,f);
	}
	de[r]=1,fa[r]=r;
	dfs1(r,r);dfs2(r,r);
	tree.build(1,1,n,a,fdfn);
	for(int i=1,op,x,y,z;i<=m;i++){
		scanf("%lld",&op);
		if(op==1){
			scanf("%lld%lld%lld",&x,&y,&z);
			x_to_y_add_z(x,y,z%MOD);
		}
		if(op==2){
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",ask_x_to_y(x,y)%MOD);
		}
		if(op==3){
			scanf("%lld%lld",&x,&z);
			x_subtree_add_z(x,z%MOD);
		}
		if(op==4){
			scanf("%lld",&x);
			printf("%lld\n",ask_x_subtree(x)%MOD);
		}
	}
	return 0;
}

细节

要注意线段树实在dfs序上建立的,修改和查询时都要用dfs序。

标签:ver,剖分,int,void,dfs,树链,add,笔记,son
From: https://www.cnblogs.com/maniubi/p/16972544.html

相关文章

  • 《深入理解计算机系统》第二章学习笔记
    补码编码是表示有符号整数的最常见的方式,有符号整数就是可以为正或者为负的数字。计算机的表示法是用有限数量的位来对一个数字编码,因此,当结果太大以至不能表示时,某些运算......
  • 微波炉变压器点焊机笔记
    为了将锂电池堆叠成锂电池组,不免需要点焊。而近期笔者一台微波炉“退休”了,刚好看到网上流传用微波炉变压器改造成点焊机的文章。动手将就地组装一台。现记录下制作心得备忘......
  • delphi D11编程语言手册 学习笔记(P393-419) 对象与内存
      这本书可以在Delphi研习社②群256456744的群文件里找到.书名:Delphi11AlexandriaEdition.pdf 这些年来,Delphi行动装置编译器提供了一个不同的内存模式,称......
  • python 笔记
    1、Myfirstprocedure。#我的第一个程序。print('Helloworld!')#print:打印到屏幕。(‘打印到屏幕的内容’)print('Ilikeyou!')#例一执行程序,输出如下:......
  • Vue2(笔记31) - 脚手架 - scoped
    scoped样式的作用域,每个组件都有独立的样式,最终都会打包合并,难免会重名导致页面样式混乱,可以给每个组件的样式加上scoped 限定样式的作用域只限于当前组件;改下school.vue......
  • Entity Framework Core 笔记 - 入门
    先决条件请确保安装了.NETCore第6或7版本,我这安装的是7.   一.领域建模方式1.CodeFirstPOCO:PlainOldCLRObject。内在含义是指那些没有从任何类继承......
  • Vue学习笔记2--类样式绑定和内置指令v-xxx
    Class和Style绑定class绑定放置对象(常用):class="{类名:布尔}"<!--第一种放置字符串--><pclass="active">hello</p><!--第二种放置对象--><!--......
  • 【图像处理笔记】傅里叶变换
    【图像处理笔记】总目录0引言在之前的博客图像增强,傅里叶变换(OpenCV)中都有用到过傅里叶变换,但一直都不是特别理解,现系统地学习一下。先来看一个视频傅里叶级数与傅立叶......
  • Vue2(笔记30) - 脚手架 - 插件
    插件Vue的插件功能可以整合之前所有的全局配置,也支持传参,使用起来比较强大;Vue 的插件,本质上是一个 对象;要求这个对象中,必须包含install() 方法;第一步:定义一个插件文件;pu......
  • #yyds干货盘点# 歌谣学前端之react笔记之学习之函数组件
    前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从......