树链剖分板子

我是在这篇博客学的:树链剖分详解 - 自为风月马前卒 - 博客园

 树链剖分是什么?以我浅薄的理解,树是一种树型结构;而链式一种线型结构。对于线型结构,我们有一个大杀器:线段树。而显然,线段树没法直接用在树型结构上。

于是我们就想到,能不能把树切成多条链,然后拼到一起,变成一个线型结构呢?

我们把一棵树切若干刀,变成多条“重链”,再拼在一起,重新编号,塞进线段树。这就是树链剖分。

洛谷板子题:【模板】轻重链剖分/树链剖分 - 洛谷

代码:

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define int long long
#define ls o<<1
#define rs o<<1|1
const int N = 1e5+5;
int n,m,root,mod;
int deep[N]; //结点深度
int fa[N]; //结点父亲
int son[N]; //结点重儿子
int tot[N]; //结点子树大小
vector<int> g[N]; //图,g[i]记录i的所有儿子
int idx[N], cnt=0; //重新编号数组
int top[N]; //top[i]表示i所在重链的头结点
int a[N];   //a[i]表示编号后i号结点的权值
int b[N];   //b[i]表示i的权值

int t[N<<2],lz[N<<2]; //线段树和懒标

/* 第一步
按照我们上面说的,我们首先要对整棵树dfs一遍,找出每个节点的重儿子
顺便处理出每个节点的深度,以及他们的父亲节点
*/
int dfs1(int u,int f,int dep){
	deep[u] = dep;
	fa[u] = f;
	tot[u] = 1;
	int sonsz = -1; //重儿子的子树大小
	for(int v:g[u]){
		if(v==f) continue; //跳过父亲
		tot[u] += dfs1(v,u,dep+1); //dfs遍历儿子
		if(tot[v] > sonsz) sonsz = tot[v], son[u] = v;
	}
	return tot[u];
}
/* 第二步
需要对整棵树进行重新编号,
我把一开始每个结点的权值存在了b数组内
*/
void dfs2(int u,int topf){
	idx[u] = ++cnt; //新编号结点
	a[cnt]=b[u]; //记录新编号权值
	top[u] = topf;
	if(!son[u]) return; //没儿子就结束
	dfs2(son[u],topf); //如果有儿子,继续dfs跑重链
	for(int v:g[u]){
		if(!idx[v]) dfs2(v,v); //对其他儿子,新开一条重链进行dfs
	}
}
/* 第三步
需要根据新编号的树,把树上每个点投射到线段树上
*/
inline void pushup(int o){t[o] = (t[ls]+t[rs]+mod)%mod;}
inline void pushdn(int o, int l, int r){
	if(!lz[o]) return; //没有懒标,结束
	int mid = l+r>>1;
	t[ls] = (t[ls]+lz[o]*(mid-l+1))%mod; //sum值修改
	t[rs] = (t[rs]+lz[o]*(r-mid))%mod;
	lz[ls] = (lz[ls]+lz[o])%mod;         //lz值修改
	lz[rs] = (lz[rs]+lz[o])%mod;
	lz[o] = 0;                           //当前lz清零
}
void build(int o,int l,int r){
	if(l==r){t[o] = a[l]; return;}
	int mid = l+r>>1;
	build(ls,l,mid); build(rs,mid+1,r);
	pushup(o);
}
void upd(int o,int l,int r,int x,int y,int val){
	if(x<=l && r<=y){
		t[o] += val*(r-l+1);
		lz[o] += val;
		return;
	}
	pushdn(o,l,r);
	int mid = l+r>>1;
	if(x<=mid) upd(ls,l,mid,x,y,val);
	if(y >mid) upd(rs,mid+1,r,x,y,val);
	pushup(o);
}
int query(int o,int l,int r,int x,int y){
	int res = 0;
	if(x<=l && r<=y) return t[o];
	pushdn(o,l,r);
	int mid = l+r>>1;
	if(x<=mid) res = (res+query(ls,l,mid,x,y))%mod;
	if(y >mid) res = (res+query(rs,mid+1,r,x,y))%mod;
	return res;
}
/* 第四步
写出四种操作
*/
void treeAdd(int x,int y,int val){ //x,y的路径上加val的权值
	while(top[x]!=top[y]){ //当不在同一条链上,就一直重复跳
		if(deep[top[x]] < deep[top[y]]) swap(x,y); //要让深度大的往上跳
		upd(1,1,n,idx[top[x]],idx[x], val); //处理这条重链的部分
		x = fa[top[x]]; //跳到链顶的fa处
	}
	if(deep[x] > deep[y]) swap(x,y);
	upd(1,1,n,idx[x],idx[y],val);
}
int treeSum(int x,int y){
	int res = 0;
	while(top[x]!=top[y]){ //当不在同一条链上,就一直重复跳
		if(deep[top[x]] < deep[top[y]]) swap(x,y); //要让深度大的往上跳
		res = (res+query(1,1,n,idx[top[x]],idx[x]))%mod; //处理这条重链的部分
		x = fa[top[x]]; //跳到链顶的fa处
	}
	if(deep[x] > deep[y]) swap(x,y);
	res = (res+query(1,1,n,idx[x],idx[y]))%mod;
	return res;
}


signed main(){
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin>>n>>m>>root>>mod;
	FOR(i,1,n) cin>>b[i];
	FOR(i,1,n-1){
		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);
	while(m--){
		int op,x,y,z; cin>>op;
		if(op==1){
			cin>>x>>y>>z; z%=mod;
			treeAdd(x,y,z);
		}
		else if(op==2){
			cin>>x>>y;
			cout<<treeSum(x,y)<<'\n';
		}
		else if(op==3){
			cin>>x>>z; z%=mod;
			upd(1,1,n,idx[x],idx[x]+tot[x]-1,z);
		}
		else if(op==4){
			cin>>x;
			cout<<query(1,1,n,idx[x],idx[x]+tot[x]-1)<<'\n';
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值