树链剖分模板

大佬博客:http://www.cnblogs.com/chinhhh/p/7965433.html#dfs1
模板题: https://www.luogu.org/problemnew/show/P3384

重儿子(非叶子结点):它某个点的作为儿子,且是儿子中子树结点数最多的儿子。
轻儿子(非叶子结点):非重儿子
重边:一个父亲连接他的重儿子的边称为重边
轻边:非重边
重链:

  1. 相邻重边连起来
  2. 连接一个重儿子
  3. 每个叶子结点,如果是轻儿子,则有一条以自己为起点的长度为1的链

把csdn当做云盘
模板

#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
typedef long long ll;
typedef pair<int,int> pii;

const int maxn = 2e5+5;
// 变量
int n,m,mod;
vector<int> G[maxn];
int w[maxn]; // 点权值
// 线段树
int val[maxn<<2], lazy[maxn<<2];
// 树链剖分
int son[maxn], id[maxn], fa[maxn],dep[maxn],siz[maxn],top[maxn],wt[maxn];
int cnt;
//------------------------------

// 线段树部分
void pushup(int rt) {
	val[rt] = (val[rt<<1]+val[rt<<1|1])%mod;
}
void pushdown(int rt, int l,int r) {
	if(lazy[rt]) {
		int mid = (l+r) >> 1;
		val[rt<<1] += lazy[rt]*(mid-l+1);
		val[rt<<1|1] += lazy[rt]*(r-mid);
		lazy[rt<<1] += lazy[rt];
		lazy[rt<<1|1] += lazy[rt];
		lazy[rt] = 0;
		val[rt<<1] %= mod;
		val[rt<<1|1] %= mod;
		lazy[rt<<1] %= mod;
		lazy[rt<<1|1] %= mod;
	}
}
void build(int l, int r,int rt) {
	if(l == r) {
		val[rt] = wt[l];
		return;
	}
	int mid = (l+r)>>1;
	build(lson);
	build(rson);
	pushup(rt);
}
void update(int L, int R, int k,int l, int r,int rt) {
	if(L > r || R < l) return;
	if(L <= l && r <= R) {
		val[rt] += k*(r-l+1);
		val[rt] %= mod;
		lazy[rt] += k;
		lazy[rt] %= mod;
		return;
	}
	int mid = (l+r)>>1;
	pushdown(rt,l,r);
	update(L,R,k,lson);
	update(L,R,k,rson);
	pushup(rt);
}
int query(int L, int R, int l,int r, int rt) {
	if(L > r || R < l) return 0;
	if(L <= l && r <= R) {
		return val[rt];
	}
	int mid = (l+r)>>1;
	int res = 0;
	pushdown(rt,l,r);
	res += query(L,R,lson);
	res %= mod;
	res += query(L,R,rson);
	res %= mod;
	pushup(rt);
	return res;
}
//------------------------------

//树链剖分,操作
// 求和
int qRange(int x,int y) {
	int ans = 0;
	while(top[x] != top[y]) { // 让其跑到一条链上
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		ans += query(id[top[x]],id[x],1,n,1);
		ans %= mod;
		x = fa[top[x]];
	}
	// 在同一条链上了
	if(dep[x] > dep[y]) swap(x,y);
	ans += query(id[x],id[y],1,n,1);
	ans %= mod;
	return ans;
}
// 更新
void updRange(int x,int y, int k) {
	k %= mod;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		update(id[top[x]],id[x],k,1,n,1);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x,y);
	update(id[x],id[y],k,1,n,1);
}
int qSon(int x) {
	return query(id[x],id[x]+siz[x]-1,1,n,1);
}
void updSon(int x, int k) {
	update(id[x],id[x]+siz[x]-1,k,1,n,1);
}
//------------------------------

// 树链剖分, 处理链
// u 当前结点,f父亲,deep深度
void dfs1(int u,int f,int deep) {
	dep[u] = deep;
	fa[u] = f;
	siz[u] = 1;
	int maxson=-1; // 重儿子子树大小
	for(int i = 0; i < G[u].size(); ++i) {
		int v = G[u][i];
		if(v == f) continue;
		dfs1(v,u,deep+1);
		siz[u] += siz[v];
		if(siz[v] > maxson) {
			son[u] = v;
			maxson = siz[v];
		}
	}
}
// u 当前结点,topf当前链的最顶端的节点
void dfs2(int u, int topf) {
	id[u] = ++cnt; // u点编号为cnt
	wt[cnt] = w[u]; // 记录新编号的权值
	top[u] = topf; // 该点所在链的链头
	if(!son[u]) return;
	dfs2(son[u], topf); // 先处理重儿子
	for(int i = 0; i < G[u].size(); ++i) {
		int v = G[u][i];
		if(v == fa[u] || v == son[u]) continue;
		dfs2(v,v); // 每个轻儿子都有从自己开始的链
	}
}
//------------------------------

int main() {
	int q,rt;
	scanf("%d%d%d%d", &n, &q, &rt, &mod);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &w[i]);
	int u,v;
	for(int i = 0; i < n-1; ++i) {
		scanf("%d%d", &u, &v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs1(rt,-1,1);
	dfs2(rt,rt);
	build(1,n,1);
	int opt,x,y,z;
	for(int i = 0; i < q; ++i) {
		scanf("%d", &opt);
		if(opt == 1) {
			scanf("%d%d%d", &x, &y, &z);
			updRange(x,y,z);
		} else if(opt == 2) {
			scanf("%d%d", &x, &y);
			printf("%d\n", qRange(x,y));
		} else if(opt == 3) {
			scanf("%d%d", &x, &z);
			updSon(x,z);
		} else if(opt == 4) {
			scanf("%d", &x);
			printf("%d\n", qSon(x));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值