首页 > 其他分享 >树链剖分

树链剖分

时间:2024-06-13 10:35:15浏览次数:21  
标签:sz 儿子 剖分 int tr 树链 编号 重链

基本概念

将树中的点重新编号,使得树中任意一条路径,都可以转化成O(logn)段连续区间
1.将一棵树转化成一个序列
2.将树中的任意一段路径转化成logn段连续区间(线段树,树状数组)

重链剖分

重儿子:子树数量最多的根节点(只有一个,多个都是,任选一个即可)
轻儿子:其余儿子
重边:重儿子和父节点的相连的边
重链:重边构成的极大路径(每个点都要包含在一个重链里面)
轻边:轻儿子和父节点相连的边

重新编号:利用dfs序给整棵树编号,要求优先遍历重儿子,即可保证重链上所有点的编号是连续的
定理:树中任意一条路径均可拆分成o(logn)个条重链,即可拆分成logn个连续区间
补充:每条重链的开头都是一个轻儿子
模板题链接

代码:

#include<bits/stdc++.h>

#define endl '\n'
#define int long long
//#define x first
//#define y second

using namespace std;

constexpr  int N=1e5+10;

typedef pair<int,int> PII;

int n,m;
vector<int> e[N];
int w[N];//点的权值 
int id[N],nw[N];//dfs序的编号,每个编号的点的权值是多少 
int cnt;
int dep[N],sz[N],top[N],fa[N],son[N];//深度,子树大小,重链的顶点,父节点,重儿子

struct Tree{//线段树辅助
	int l,r;
	int add,sum;
}tr[N*4]; 

void dfs1(int u,int dad){//预处理出每科子树的大小
	dep[u]=dep[dad]+1,sz[u]=1;
	for(auto v:e[u]){
		if(v==dad) continue;
		fa[v]=u;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[son[u]]<sz[v]) son[u]=v; 
	}
}

void dfs2(int u,int t){//预处理出dfs序编号,以及该编号节点的权值大小
	id[u]=++cnt,nw[cnt]=w[u],top[u]=t;
	if(!son[u]) return ;
	dfs2(son[u],t);
	for(auto v:e[u]){
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);//每条重链的开头都是一个轻儿子 
	}
} 

void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}

void pushdown(int u){
	auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	if(root.add){
		left.add+=root.add,left.sum+=root.add*(left.r-left.l+1);
		right.add+=root.add,right.sum+=root.add*(right.r-right.l+1);
		root.add=0;
	}
}

void build(int u,int l,int r){
	tr[u]={l,r,0,nw[r]};
	if(l==r) return ;
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	pushup(u);
} 

void update(int u,int l,int r,int k){
	if(l<=tr[u].l&&r>=tr[u].r){
		tr[u].add+=k;
		tr[u].sum+=k*(tr[u].r-tr[u].l+1);
		return ;
	}
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid) update(u<<1,l,r,k);
	if(r>mid) update(u<<1|1,l,r,k);
	pushup(u);
}

int query(int u,int l,int r){
	if(l<=tr[u].l&&r>=tr[u].r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	int res=0;
	if(l<=mid) res+=query(u<<1,l,r);
	if(r>mid) res+=query(u<<1|1,l,r);
	return res;
}

void update_path(int u,int v,int k){//更新一条路径
	while(top[u]!=top[v]){//类似于lca,先不断处理较低点的重链,直到二者重合
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u],k);
		u=fa[top[u]];
	}
	
	if(dep[u]<dep[v]) swap(u,v);
	update(1,id[v],id[u],k);
}

int query_path(int u,int v){//查询一条路径 
	int res=0;
	while(top[u]!=top[v]){//类似于lca,先不断处理较低点的重链,直到二者重合
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res+=query(1,id[top[u]],id[u]);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	res+=query(1,id[v],id[u]);
	return res;
}

void update_tree(int u,int k){//更新一个子树的区间和
	update(1,id[u],id[u]+sz[u]-1,k);
}

int query_tree(int u){//查询一颗子树的区间和
	return query(1,id[u],id[u]+sz[u]-1);
}

void slove() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	dfs1(1,0);//预处理出重儿子
	dfs2(1,1);//求一下dfs序 
	build(1,1,n);
// 	cout<<query(1,5,5)<<endl;
	cin>>m;
	while(m--){
		int t,u,v,k;
		cin>>t>>u;
		if(t==1){
			cin>>v>>k;
			update_path(u,v,k);
		}
		else if(t==2){
			cin>>k;
			update_tree(u,k);
		}
		else if(t==3){
			cin>>v;
			cout<<query_path(u,v)<<endl;
		}
		else {
			cout<<query_tree(u)<<endl;
		}
	}
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T=1;
//    cin>>T;
    while(T--) slove();
}

标签:sz,儿子,剖分,int,tr,树链,编号,重链
From: https://www.cnblogs.com/mendax-Z/p/18245362

相关文章

  • 二叉树链式结构的前序、中序、后序、层序遍历
    文章目录一、二叉树创建二、前序遍历概念以及解释代码三、中序遍历概念及解释代码四、后序遍历概念及解释代码五、层序遍历概念及解释代码一、二叉树创建&mesp; 实现二叉树的遍历,我们要先手搓出一个二叉树,在次基础上实现二叉树的前序、中序、后序、层序遍历。......
  • 树链剖分总结
    本博客为本人对树剖可解决题目的总结。。。概念1:\(fa[u]\):\(u\)的父亲2:\(size[u]\):\(u\)节点\(u\)为子树的节点个数3:\(dep[u]\):\(u\)节点的深度4:\(wson[u]\):\(u\)节点的重儿子编号5:\(top[u]\):\(u\)节点所在重链的顶部端点6:\(dfn[u]\):\(u\)节点......
  • 树链剖分
    树链剖分把一棵树剖分成若干条链,然后对链进行操作,维护路径上的信息。有点像分块,也是暴力做法,单个操作太慢就整个操作。我们一般用重链剖分,也就是根据子树大小把边分成轻重边。重链剖分定义重儿子:所有子节点中子树最大的节点,多个一样的取任意。轻儿子:不是重儿子的就是轻儿......
  • 树链剖分
    树链剖分额一个怎么说感觉很鸡肋的东西,它似乎就是普通线段树加上了个路径上的修改和查询(暂时所学),但是肯定比线段树灵活。不多说,先看成果:::如何处理树上一个点到另一个点链上的操作?如果考虑暴力的话,肯定是不可行的,因为当这个树变成类似一条链的时候,我们的复杂度就可能被卡到惊人......
  • 树链剖分代码细解
    总结回顾类文章,酌情观看。ShapeOfYou头图Linux找图太难了呜呜Theclubisn'tthebestplacetofindaloverSothebariswhereIgoMeandmyfriendsatthetabledoingshotsDrinkingfasterandthenwetalkslowYoucomeoverandstartupaconversat......
  • 树链剖分 学习笔记
    裂缝中的阳光-林俊杰头图有多少创伤卡在咽喉有多少眼泪滴湿枕头有多少你觉得不能够被懂的痛只能沉默有多少夜晚没有尽头有多少的寂寞无人诉说有多少次的梦还没做已成空等到黑夜翻面之后会是新的白昼等到海啸退去之后只是潮起潮落别到最后你才发觉心里头的......
  • 树链剖分 学习笔记
    树链剖分学习笔记时更。还没开始学,放个板子先。板子#include<bits/stdc++.h>#definefo(x,y,z)for(int(x)=(y);(x)<=(z);(x)++)#definefu(x,y,z)for(int(x)=(y);(x)>=(z);(x)--)typedeflonglongll;inlineintqr(){ charch=getchar();intx=0,f=1; for(;ch......
  • 长链剖分
    我们现在有一棵有根树(节点的深度定义为根到它的距离)。设节点\(u\)的所有儿子中,深度最大的点为它的长儿子,记作\(son_u\)。(存在多个则任取一个,没有儿子则为空)记每个节点到它的长儿子的变为长边,其余边为短边。一段极长的全部由长边组成的链称为长链。特别的,按此过程划分后,不在......
  • 树链剖分
    树链剖分在DFS树上把连续的一段有祖先关系的单独开一个序列存储。查询每一个位置,不断地往链头条,然后跳到链头的父亲的链上\(\dots\)如果按DFS徐直接搞,会被以下数据hack可行的序列有\(:[110],[2,10],[3,12],[4,13],[5,14],[6,15],[7,16],[8,17],......
  • 树链剖分
    树链剖分我们一般使用重链剖分,就是子树大的先剖分,应为这样可以保证时间在\(log_n\)如图,先按照\(dfs\)序遍历出有一棵树,那么\(dfs\)序就是\([1,2,3,4,5,6,7,8,9]\),如果碰到一条边上\(dfn[f]-dfn[u]!=1\)则断一次,就可以剖出链了,如图,链为\([1,2,3][4,5]......