树链剖分

我来了,我来了,我来了,啊啊啊啊啊啊啊啊
我A了,来了, 我太激动了在这里插入图片描述

题目

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

❶换根:将一个指定的节点设置为树的新根。
❷修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。
❸修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。
❹询问路径:询问某条路径上节点的权值和。
❺询问子树:询问某个子树内节点的权值和。

输入格式
第一行一个整数 n,表示节点的个数。

第二行n个整数表示第 i 个节点的初始权值 。

第三行n-1个整数,表示 i+1 号节点的父节点编号 。

第四行一个整数 m,表示操作个数。

接下来 m 行,每行第一个整数表示操作类型编号:

❶若类型为 1,则接下来一个整数 u,表示新根的编号。
❷若类型为 2,则接下来三个整数u,v,k分别表示路径两端的节点编号以及增加的权值。
❸若类型为 3,则接下来两个整数 u,k分别表示子树根节点编号以及增加的权值。
❹若类型为 4,则接下来两个整数 u,v表示路径两端的节点编号。
❺若类型为 5,则接下来一个整数 u,表示子树根节点编号。

输出格式
对于每一个类型为 4 或 5 的操作,输出一行一个整数表示答案。

样例
样例输入
6
1 2 3 4 5 6
1 2 1 4 4
6
4 5 6
2 2 4 1
5 1
1 4
3 1 2
4 2 5
样例输出
15
24
19

额外赠送数据一个:
6
1 2 3 4 5 6
1 2 1 4 4
1
1 6
5 4
输出:
15

数据范围与提示
对于100% 的数据,1≤n,m,k,ai≤105。数据有一定梯度。

题解

这道题也是一道树链剖分的模板,不了解的亲故可移步
树链剖分入门

其实这道题的2,3,4,5类型的操作都是模板可以直接使用
but唯一的难点就是换根这个操作

相信很多朋友都会想到每次遇到1操作,就重新建树
在这里插入图片描述
用脚想都知道这么建下去,编译器就要被加班卡死了
所以我们必须找到一个nb办法——不换根
只是每次遇到1操作,就把root赋成u即可

我们考虑其实以任何点为根,对路径的更改和查询是不会影响的
因为这是一棵树,两点之间的路径是唯一的,永远都是那么走,
至于根在哪?
在这里插入图片描述
那么对于子树的查询和修改就比较在这里插入图片描述

有两种情况
Ⅰ:当前的根root未被u包含
也就是我们以1为根初建树时,root就不属于u的子树
那么易知此时的root不会影响有关u的子树操作
即u原本子树包含哪些,假设重新建树后也不会有所变换
此时就按照模板去查询或更改即可

Ⅱ:根root在u的子数范围内
这种情况就有点南翔了
在这里插入图片描述
这个时候我们就要搞懂dfs建树时的走向
经过分析你会发现,root成为根之后,它所在的那一条链都将脱离u的殖民统治,成为一个独立自主的新链或新子树

那么此时我们只需要暴力(不会TLE 在这里插入图片描述)去爬链,
找到root所在u子树中的这条链,再减去即可完成

最后有几点需要注意的
1)当u==root的时候,特别处理一下,就是query(1,n)
不然会RE在这里插入图片描述
2)一定要记得开long long,
特别是query的左右子树查询用临时变量sum储存的地方,我错了十次在这里插入图片描述
3)有的亲故可能会用lca去判断u,root是否在一个子树内
其实不必,因为我们强大的dfs保证了i点的子树id编号一定是连续的
所以你直接用id进行比较即可
在这里插入图片描述

代码实现

#include <cstdio>
#include <vector>
using namespace std;
#define MAXN 100005
#define LL long long
vector < int > G[MAXN];
int n, m, cnt;
int a[MAXN];
int f[MAXN], id[MAXN], top[MAXN], dep[MAXN], son[MAXN], tot[MAXN];
LL tree[MAXN << 2], lazy[MAXN << 2];

void dfs1 ( int u, int fa, int depth ) {
	dep[u] = depth;
	tot[u] = 1;
	for ( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		if ( v == fa ) continue;
		dfs1 ( v, u, depth + 1 );
		tot[u] += tot[v];
		if ( tot[v] > tot[son[u]] || ! son[u] )
			son[u] = v;
	}
}

void dfs2 ( int u, int t ) {
	id[u] = ++ cnt;
	top[u] = t;
	if ( ! son[u] ) return;
	dfs2 ( son[u], t );
	for ( int i = 0;i < G[u].size();i ++ ) {
		int v = G[u][i];
		if ( v == son[u] || v == f[u] ) continue;
		dfs2 ( v, v );
	}
}

void pushdown ( int l, int r, int num ) {
	int mid = ( l + r ) >> 1;
	tree[num << 1] += lazy[num] * ( mid - l + 1 );
	tree[num << 1 | 1] += lazy[num] * ( r - mid );
	lazy[num << 1] += lazy[num];
	lazy[num << 1 | 1] += lazy[num];
	lazy[num] = 0;
}

void update ( int l, int r, int L, int R, int num, LL val ) {
	if ( L <= l && r <= R ) {
		tree[num] += val * ( r - l + 1 );
		lazy[num] += val;
		return;
	}
	pushdown ( l, r, num );
	int mid = ( l + r ) >> 1;
	if ( L <= mid ) update ( l, mid, L, R, num << 1, val );
	if ( mid < R ) update ( mid + 1, r, L, R, num << 1 | 1, val );
	tree[num] = tree[num << 1] + tree[num << 1 | 1];
}

LL query ( int l, int r, int L, int R, int num ) {
	if ( L <= l && r <= R ) return tree[num];
	pushdown ( l, r, num );
	int mid = ( l + r ) >> 1;
	LL sum1 = 0, sum2 = 0;
	if ( L <= mid ) sum1 = query ( l, mid, L, R, num << 1 );
	if ( mid < R ) sum2 = query ( mid + 1, r, L, R, num << 1 | 1 );
	return sum1 + sum2;
}

void solve_sum ( int x, int y, bool flag, LL val ) {
	LL ans = 0;
	int fx = top[x], fy = top[y];
	while ( fx != fy ) {
		if ( dep[fx] >= dep[fy] ) {
			if ( flag ) ans += query ( 1, cnt, id[fx], id[x], 1 );
			else update ( 1, cnt, id[fx], id[x], 1, val );
			x = f[fx];
			fx = top[x];
		}
		else {
			if ( flag ) ans += query ( 1, cnt, id[fy], id[y], 1 );
			else update ( 1, cnt, id[fy], id[y], 1, val );
			y = f[fy];
			fy = top[y];
		}
	}
	if ( id[x] <= id[y] ) {
		if ( flag ) ans += query ( 1, cnt, id[x], id[y], 1 );
		else update ( 1, cnt, id[x], id[y], 1, val );
	}
	else {
		if ( flag ) ans += query ( 1, cnt, id[y], id[x], 1 );
		else update ( 1, cnt, id[y], id[x], 1, val );
	}
	if ( flag ) 
		printf ( "%lld\n", ans );
}

int find_top ( int u, int t ) {
	while ( f[u] != t )
		u = f[u];
	return u;
}

int main() {
	scanf ( "%d", &n );
	for ( int i = 1;i <= n;i ++ )
		scanf ( "%d", &a[i] );
	for ( int i = 1;i < n;i ++ ) {
		scanf ( "%d", &f[i + 1] );
		G[f[i + 1]].push_back ( i + 1 );
	}
	dfs1 ( 1, 0, 1 );
	dfs2 ( 1, 1 );
	for ( int i = 1;i <= n;i ++ )
		update ( 1, cnt, id[i], id[i], 1, a[i] ); 
	int root = 1;
	scanf ( "%d", &m );
	for ( int i = 1;i <= m;i ++ ) {
		int opt, u, v;
		LL k;
		scanf ( "%d", &opt );
		if ( opt == 1 ) {
			scanf ( "%d", &u );
			root = u;
		}
		if ( opt == 2 ) {
			scanf ( "%d %d %lld", &u, &v, &k );
			solve_sum ( u, v, 0, k );
		}
		if ( opt == 3 ) {
			scanf ( "%d %lld", &u, &k );
			if ( root == u )
				update ( 1, cnt, 1, n, 1, k );
			else {
				if ( id[root] < id[u] || id[root] > id[u] + tot[u] - 1 ) 
					update ( 1, cnt, id[u], id[u] + tot[u] - 1, 1, k );
				else {
					int son = find_top ( root, u );
					update ( 1, cnt, 1, n, 1, k );
					update ( 1, cnt, id[son], id[son] + tot[son] - 1, 1, -k );
				}
			}
		}
		if ( opt == 4 ) {
			scanf ( "%d %d", &u, &v );
			solve_sum ( u, v, 1, 0 ); 
		}
		if ( opt == 5 ) {
			scanf ( "%d", &u );
			if ( root == u )
				printf ( "%lld\n", query ( 1, cnt, 1, n, 1 ) );
			else {
				if ( id[root] < id[u] || id[root] > id[u] + tot[u] - 1 ) 
					printf ( "%lld\n", query ( 1, cnt, id[u], id[u] + tot[u] - 1, 1 ) );
				else {     
					int son = find_top ( root, u );
					printf ( "%lld\n", query ( 1, cnt, 1, n, 1 ) - query ( 1, cnt, id[son], id[son] + tot[son] - 1, 1 ) );
				}
			}
		}
	}
	return 0;
}

这道题卡了本仙女将近两个星期,唉
在这里插入图片描述在这里插入图片描述
好了,有什么问题或不懂的欢迎评论,让我们满怀新激情去跳下一个坑!
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值