树链剖分

题目:
这是一道模板题。

给定一棵n个节点的树,初始时该树的根为1号节点,每个节点有一个给定的权值。下面依次进行m个操作,操作分为如下五种类型:

换根:将一个指定的节点设置为树的新根。

修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。

修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。

询问路径:询问某条路径上节点的权值和。

询问子树:询问某个子树内节点的权值和。

贴个题目防止搜不到题。

感觉这题根本不是一道模板题。(自己就是这样被坑进来的)

看到换根我就蒙了。其实可以发现,路径与根是无关的。

据大佬手推,改根的情况可以分成两种:

  1. root不在u的子树中时 操作按常规进行。
  2. root在u的子树中时 除了root的子树和root与u中间这一坨点,u都可以遍及。

这点想了好久,之前想的是用u的字数和减去root的子树和u与root中间的一坨点。(我还想过root与u中间的一坨点是链

上马咯!

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
const int N = 100002;
typedef long long ll;
int q, n, w[N], top[N], son[N], d[N], id[N], f[N], s[N], cnt, root = 1;
vector <int> G[N];
ll st[N << 2], la[N << 2];
void init(const int x, const int ba) {
	d[x] = d[ba] + 1;
	s[x] = 1;
	f[x] = ba;
	for(int i = 0; i < G[x].size(); i ++) {
		int v = G[x][i];
		if(v == ba)	
			continue;
		init(v, x);
		s[x] += s[v];
		if(s[v] > s[son[x]] || son[x] == 0)
			son[x] = v;
	}
}
void dfs(const int x, const int t) {
	top[x] = t;
	id[x] = ++ cnt;
	if(son[x] == 0)
		return;
	dfs(son[x], t);
	for(int i = 0; i < G[x].size(); i ++) {
		int v = G[x][i];
		if(v == f[x] || v == son[x])
			continue;
		dfs(v, v);
	}
}
void pushDown(const int x, const int L, const int R) {
	if(! la[x])
		return;
	int l = (x << 1), r = (x << 1 | 1), mid = L + R >> 1;
	st[l] += la[x] * (mid - L + 1);
	st[r] += la[x] * (R - mid);
	la[l] += la[x];
	la[r] += la[x];
	la[x] = 0;
}
void add(const int x, const int l, const int r, const int L, const int R, const int k) {
	if(l > R || r < L)
		return;
	if(l >= L && r <= R) {
		st[x] += 1ll * k * (r - l + 1);
		la[x] += k;
		return;
	}
	pushDown(x, l, r);
	int mid = l + r >> 1;
	add(x << 1, l, mid, L, R, k);
	add(x << 1 | 1, mid + 1, r, L, R, k);
	st[x] = st[x << 1] + st[x << 1 | 1];
}
ll ask(const int x, const int l, const int r, const int L, const int R) {
	if(l > R || r < L)
		return 0;
	if(l >= L && r <= R)
		return st[x];
	pushDown(x, l, r);
	int mid = l + r >> 1;
	return ask(x << 1, l, mid, L, R) + ask(x << 1 | 1, mid + 1, r, L, R);
}
int main() {
	int tt, flag, a, b;
	ll ans;
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++)
		scanf("%d", &w[i]);
	for(int i = 1; i < n; i ++) {
		scanf("%d", &tt);
		G[tt].push_back(i + 1);
	}
	init(1, 0);
	dfs(1, 1);
	for(int i = 1; i <= n; i ++)
		add(1, 1, n, id[i], id[i], w[i]);
	scanf("%d", &q);
	while(q --) {
		scanf("%d", &flag);
		switch(flag) {
			case 1 : {
				scanf("%d", &tt);
				root = tt;
				break;
			}
			case 2 : {
				scanf("%d %d %d", &a, &b, &tt);
				while(top[a] != top[b]) {
					if(d[top[a]] <= d[top[b]]) {
						add(1, 1, n, id[top[b]], id[b], tt);
						b = f[top[b]];
					}
					else {
						add(1, 1, n, id[top[a]], id[a], tt);
						a = f[top[a]];
					}
				}
				if(id[a] <= id[b])
					add(1, 1, n, id[a], id[b], tt);
				else
					add(1, 1, n, id[b], id[a], tt);
				break;
			}
			case 3 : {
				scanf("%d %d", &a, &tt);
				if(id[a] + s[a] - 1 >= id[root] && id[a] < id[root]) {
					add(1, 1, n, 1, n, tt);
					b = root;
					while(a != f[b])
						b = f[b];
					add(1, 1, n, id[b], id[b] + s[b] - 1, -tt);
				}
				else if(root == a)
					add(1, 1, n, 1, n, tt);
				else
					add(1, 1, n, id[a], id[a] + s[a] - 1, tt);
				break;
			}
			case 4 : {
				ans = 0;
				scanf("%d %d", &a, &b);
				while(top[a] != top[b]) {
					if(d[top[a]] < d[top[b]]) {
						ans += ask(1, 1, n, id[top[b]], id[b]);
						b = f[top[b]];
					}
					else {
						ans += ask(1, 1, n, id[top[a]], id[a]);
						a = f[top[a]];
					}
				}
				if(id[a] < id[b])
					ans += ask(1, 1, n, id[a], id[b]);
				else
					ans += ask(1, 1, n, id[b], id[a]);
				printf("%lld\n", ans);
				break;
			}
			default : {
				scanf("%d", &a);
				if(id[a] + s[a] - 1 >= id[root] && id[a] < id[root]) {
					ans = ask(1, 1, n, 1, n);
					b = root;
					while(a != f[b])
						b = f[b];
					printf("%lld\n", ans - ask(1, 1, n, id[b], id[b] + s[b] - 1));
				}
				else if(root == a)
					printf("%lld\n", ask(1, 1, n, 1, n));
				else
					printf("%lld\n", ask(1, 1, n, id[a], id[a] + s[a] - 1));
				break;
			}
		}
	}
	return 0;
}

记得特判。

好了就讲到这里了,如有错误请大佬在评论区留言,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值