首页 > 其他分享 >树剖详解+习题

树剖详解+习题

时间:2023-03-18 22:47:11浏览次数:52  
标签:val 树剖 int tag 详解 习题 重链 节点 mod

主要思想

树链剖分(简称树剖)的思想在于将一棵树剖分为若干条链,从而转化为一个线性序列并使用数据结构维护来解决问题。

以下主要讲两种:一种是重链剖分,一种是长链剖分

重链剖分

原理

重链剖分可以将树上的任意一条路径划分成不超过 \(O(\log n)\) 条连续的链,同时通过一个特殊的 dfs 保证同一条链上节点的 \(dfn\) 连续从而可以十分方便地使用线段树等数据结构进行维护树上一条路径的信息。

同时,每条链上节点的深度都是互不相同的,所以树剖也可以用于求解 LCA。

这里列出一些使用场景:

  • 维护树上路径上的信息。

  • 维护以一个节点为根的一棵子树上的信息。

实现

首先,给出一些定义:

重子节点:其子节点中拥有最大子树(以该儿子节点为根)的儿子节点。如果有多个随便取一个,如果是叶子节点则无重子节点。

轻子节点:不是重子节点的节点。

重边:该节点到它的重子节点的边。

轻边:该节点到它的轻子节点的边。

重链:若干条首尾衔接的重边。

如果我们把单独的一个节点也当作一条重链,那么整棵树就被剖分为若干条重链。

图中每一个颜色都代表着一条重链。

实现树剖,我们需要两次 dfs。

第一次 dfs 记录每个结点的父节点、深度、子树大小以及重子节点。

第二次 dfs 记录特殊 dfs 序下的 \(dfn\)(特殊 dfs 序为优先遍历重儿子。因为这样方便记录重链并且保证重链的 \(dfn\) 连续)、每条重链的链顶以及每个 \(dfn\) 对应的节点编号(可是我不知道有啥用)。

习题1 【模板】重链剖分/树链剖分

思路

重链剖分,然后线段树维护即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,m,root,mod;
int val[maxn],cnt,size[maxn],son[maxn],top[maxn],dep[maxn],f[maxn],valt[200005],id[maxn];
struct node{
	int val,tag,l,r;
}a[maxn*4];
vector<int> G[maxn];
void down(int v){
	a[v*2].tag=(a[v*2].tag+a[v].tag)%mod;
	a[v*2+1].tag=(a[v*2+1].tag+a[v].tag)%mod;
	a[v*2].val=(a[v*2].val+a[v].tag*(a[v*2].r-a[v*2].l+1))%mod;
	a[v*2+1].val=(a[v*2+1].val+a[v].tag*(a[v*2+1].r-a[v*2+1].l+1))%mod;
	a[v].tag=0;
}
void add(int u,int l,int r,int v){
	if(l<=a[u].l&&r>=a[u].r){
		a[u].val=(a[u].val+v*(a[u].r-a[u].l+1))%mod;
		a[u].tag=(a[u].tag+v)%mod;
		return;
	}
	if(a[u].tag){
		down(u);
	}
	int mid=(a[u].l+a[u].r)/2;
	if(l<=mid){
		add(u*2,l,r,v);
	}	
	if(r>mid){
		add(u*2+1,l,r,v);
	}
	a[u].val=(a[u*2].val+a[u*2+1].val)%mod;
}
int find(int u,int l,int r){
	int val1=0;
	if(l<=a[u].l && r>=a[u].r){
    	a[u].val=a[u].val%mod;
    	return a[u].val;
	}
	if(a[u].tag){
		down(u);
	}
	int mid=(a[u].l+a[u].r)/2;
	if(l<=mid){
		val1=(find(u*2,l,r)+val1)%mod;
	}
	if(r>mid){
		val1=(find(u*2+1,l,r)+val1)%mod;
	}
	return val1;
}
void dfs1(int u,int fa,int depth){
	dep[u]=depth;
	f[u]=fa;
	size[u]=1;
	for(int i=0;i<G[u].size();i++){
		int v=G[u][i];
		if(v!=fa){
			dfs1(v,u,depth+1);
			size[u]+=size[v];
			if(size[v]>size[son[u]]){
				son[u]=v;
			}
		}
	}
}
void dfs2(int u,int nowtop){
	id[u]=++cnt;
	valt[cnt]=val[u];
	top[u]=nowtop;
	if(son[u]){
		dfs2(son[u],nowtop);
		for(int i=0;i<G[u].size();i++){
			int v=G[u][i];
			if(v!=f[u]&&v!=son[u]){
				dfs2(v,v);
			}
		}
	}
}
void build(int u,int l,int r){
	a[u].l=l;
	a[u].r=r;
	a[u].tag=0;
	if(l==r){
		a[u].val=valt[l];
		a[u].val=a[u].val%mod;
		return;
	}
	int mid=(l+r)/2;
	build(u*2,l,mid);
	build(u*2+1,mid+1,r);
	a[u].val=(a[u*2].val+a[u*2+1].val)%mod;
}
void updateintree(int x,int y,int val1){
	val1%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])
		swap(x,y);
		add(1,id[top[x]],id[x],val1);
		x=f[top[x]];
	}
	if(dep[x]>dep[y])
	swap(x,y);
	add(1,id[x],id[y],val1);
}
int queryintree(int x,int y){
	int val2=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])
		swap(x,y);
		val2=find(1,id[top[x]],id[x])+val2;
		val2%=mod;
		x=f[top[x]];
	}
	if(dep[x]>dep[y])
	swap(x,y);
	val2=find(1,id[x],id[y])+val2;
	val2%=mod;
	return val2;
}
void update(int root3,int val21){
	add(1,id[root3],id[root3]+size[root3]-1,val21);
}
int query(int root4){
	int val3=0;
	val3=find(1,id[root4],id[root4]+size[root4]-1)%mod;
	return val3;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>root>>mod;
	for(int i=1;i<=n;i++){
		cin>>val[i];
	    val[i]%=mod;
	}
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs1(root,0,1);
	dfs2(root,root);
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int op,x,y,z;
		cin>>op;
		if(op==1){
			cin>>x>>y>>z;
			updateintree(x,y,z);
		}
		if(op==2){
			cin>>x>>y;
			cout<<queryintree(x,y)<<endl;
		}
		if(op==3){
			cin>>x>>y;
			update(x,y);
		}
		if(op==4){
			cin>>x;
			cout<<query(x)<<endl;
		}
	}
	return 0;
}

标签:val,树剖,int,tag,详解,习题,重链,节点,mod
From: https://www.cnblogs.com/luqyou/p/17232045.html

相关文章

  • JavaScript 数据类型详解
    原文链接:​   ​​https://note.noxussj.top/?source=51cto​​常见的ES5数据类型分为基本数据类型、引用数据类型两种。包含字符串、数字、对象、数组、函数、布尔值......
  • webpack性能优化(2):splitChunks用法详解
    之前写的《​​webpack性能优化(0):webpack性能优化概况-优化构建速度​​​》、《​​webpack性能优化(1):分隔/分包/异步加载+组件与路由懒加载​​》如果使用vue-cli,默认......
  • YOLO详解------YOLOV1
    CV小白说YOLOV1题外话:目标检测是什么?它是在图像中对一类或多类感兴趣的目标进行查找和分类,确定它们的类别和位置。由于各类物体有不同的外观、形状和姿态,加上成像时各......
  • YOLO详解------YOLOV1
    CV小白说YOLOV1题外话:目标检测是什么?它是在图像中对一类或多类感兴趣的目标进行查找和分类,确定它们的类别和位置。由于各类物体有不同的外观、形状和姿态,加上成像时各......
  • JS数组reduce()方法详解及高级技巧
        参考:https://www.cnblogs.com/webSnow/p/15262337.html......
  • Git详解
    Git的详解一.git的全局设置gitconfig--globaluser.name"username"gitconfig--globaluser.email"email"二.Git的基础命令(在需要目录下打开Gitbash窗口)1......
  • Tarjan算法详解
    Tarjan算法介绍Tarjan算法是用于在有向图中求强连通分量的算法这里给出强连通分量的定义:有向图中一个最大的图,使这个图中每个两点都能够互相到达。这个最大的图称为强连......
  • storybook组件属性详解:组件props到strorybook Args
    首先我们查看官方文档:https://storybook.js.org/docs/vue/writing-docs/doc-block-argstable#customizing官方的例子么有看到v-model如何处理,数组、对象等复杂属性定义。......
  • 响应式编程详解,带你熟悉Reactor响应式编程
    文章目录​​一、什么是响应式编程​​​​1、Java的流和响应式流​​​​2、Java中响应式的使用​​​​3、Reactor中响应式流的基本接口​​​​4、Reactor中响应式接口的......
  • jvm jstat -gcutil 参数详解
    jstat-gcutil854410008544进程ID,用jps命令查出1000单位毫秒,每秒读取一次S0survivor0使用百分比S1survivor1使用百分比EEden区使用百分比O老年代使用百分比M......