【洛谷 P3384】树链剖分【详解树链剖分】

题意:

一颗 N N 个节点的树,每个节点上都有初始权值。现在有四种操作:
操作 1 1 —— 1   x   y   z 1\ x\ y\ z ,表示将 x x y y 节点最短路径上所有节点的值加z
操作 2 2 —— 2   x   y 2\ x\ y ,表示求 x x y y 节点最短路径上所有节点值之和
操作 3 3 —— 3   x   z 3\ x\ z ,表示将以 x x 为根节点的子树内所有节点值加 z z
操作 4 4 —— 4   x 4\ x ,表示求以 x x 为根节点的子树内所有节点值之和


思路:

既然是树链剖分模板题,因此我们只需来讨论树剖的一些实现即可。
树剖主要由三部分组成,两个 d f s dfs 与建树。
而查询与修改也由两部分组成,确定路径上的每一条链与直接在连续的链上修改。

首先我们先介绍第一个 d f s dfs ,第一个 d f s dfs 的主要目的就是处理出每个节点的子树大小、父节点、深度及其重儿子,分别为 s i z e [ x ] f a [ x ] d [ x ] s o n [ x ] size[x]、fa[x]、d[x]、son[x] 。此处主要关注的是重儿子这个概念,重儿子的定义是当前节点的子节点中,子树大小最大的点即为当前节点的重儿子。而重儿子的作用主要是在第二个 d f s dfs 中确定重链,即一条路径上均为重儿子的路径。

然后我们来介绍第二个 d f s dfs ,这个 d f s dfs 的主要目的就是求出每个节点的 d f s dfs 序,维护当前节点所在链的顶端节点。树也是在这个时候被划分为了多条链,因此被称为树链剖分。如下图所示,被覆盖了颜色的链便是重链,每个点上的数字就是节点对应的 d f s dfs 序。重链的目的是将树分为尽可能少的链,因为重链包含的节点最多。分完树链之后,我们需要让属于同一条链的节点的 d f s dfs 序连续,这样才可以在 d f s dfs 序上建树。因此 d f s dfs 遍历时,需要优先遍历重儿子。此处还有个需要注意的问题,我们在遍历的时候需要记录每个节点所属链条的顶端节点,即 t o p [ x ] top[x]
在这里插入图片描述
现在来到了第三步,即 d f s dfs 序上建树。当前线段树的区间为 [ 1 , 14 ] [1,14] ,对于每个区间维护区间节点权值累加和 s u m sum 。现在我们来解决本题要求的四个操作,首先是第一个操作,将 x > y x->y 路径上所有点权值 + z +z ,例如 x = 6 y = 10 x = 6,y = 10 。首先我们需要知道,树上线段树和普通的序列线段树最大的区别在于,对序列上两点之间的所有点权值 + z +z ,我们可以直接得到要修改的这个区间,而对于树来说,最大的问题在于无法直接得到线段树上连续的一段区间。就拿当前例子来说,修改 6 > 10 6->10 之间所有节点,其中包含了 6   4   3   2   1   7   8   10 6\ 4\ 3\ 2\ 1\ 7\ 8\ 10 ,无法直接进行修改。为了解决这个问题,我们引入了树链。

首先可以明确树链上的点,编号都是连续的,因此如果两点在同一条树链上的话,我们是可以直接在线段树上找到其对应的连续区间的。而如果两点不在同一树链上,我们就需要将他们之间的路径拆成多个树链,然后对每个树链进行更新。如修改 6 > 10 6->10 之间所有节点,我们只需要修改 6 1 > 4 7 > 8 10 6,1->4,7->8,10 这四个连续区间。于是问题就转换成了如何快速定位两点之间包含的所有树链。

要解决快速定位树链的问题,就又回归到了我们之前记录的 t o p [ x ] top[x] 数组,类似于倍增的方法,我们用 t o p top 数组来实现快速查询。继续以当前例子为例,我们首先查询 6 6 10 10 t o p top 值,发现 t o p [ 6 ] = 6 , t o p [ 10 ] = 10 top[6] = 6, top[10] = 10 ,两点不在同一链上,然后选择深度更深的点 6 6 ,可以发现 6 > t o p [ 6 ] 6->top[6] 之间的点一定包含在 6 > 10 6->10 的路径上,因此我们更新区间 [ 6 , t o p [ 6 ] ] [6,top[6]] ,然后令 6 = f a [ t o p [ 6 ] ] = 4 6 = fa[top[6]] = 4 ,就变成了求 4 > 10 4->10 路径上所有链的问题,观察代码稍加模拟就可以发现处理出了之后的三条链,到此操作 1 1 即可完成。

然后是操作 2 2 ,求 x > y x->y 路径上所有节点值之和,同操作 1 1 类似,先定位出路径上包含哪几条树链,然后在树链上直接求 s u m sum 和即可。

再来看操作 3 3 和操作 4 4 ,都是对子树进行的修改和查询,而由 d f s dfs 序的性质可以知道,点 x x 与点 x x 子树中所有节点编号都是连续的,因此直接定位线段树上 [ x , x + s i z e [ x ] 1 ] [x,x+size[x]-1] 这个区间进行修改即可。到此本题即可完成。

总结一下,建树初始化只有三步,两个dfs和一个建树,树上查询与修改只有两步,划分出两点路径上所有的链 与 直接在链上修改和查询。


代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
typedef long long ll;
typedef double db;
#define int long long
const db EPS = 1e-9;
const int N = 1e5+100;
using namespace std;

struct Edge { int next,to;} e[2*N];
struct Node { int l,r,ls,rs,sum,lazy;} t[2*N];
int n,m,root,rt,mod,val[N],head[N],tot,fa[N],d[N],son[N],size[N],top[N],id[N],rk[N];
//top[x]: x节点所在链的顶端节点, id[x]: 节点dfs序, rk[x]: dfs序对应的节点
//val[x]: 每个点初始权值, fa[x]: 每个点父节点, d[x]: 节点深度, size[x]: 节点子树大小
//rt: 线段树根节点编号
void init(){
	memset(head,0,sizeof head);
	tot = 1, size[0] = 0;
}

void add(int x, int y){
	e[++tot].next = head[x], e[tot].to = y, head[x] = tot;
}

void dfs1(int x){	//求出每个点的子树大小、深度、重儿子
	size[x] = 1, d[x] = d[fa[x]]+1, son[x] = 0;
	for(int v,i = head[x]; i; i = e[i].next)
		if((v = e[i].to)!=fa[x]){
			fa[v] = x, dfs1(v), size[x] += size[v];
			if(size[son[x]] < size[v])
				son[x] = v;
		}
}

void dfs2(int x, int tp){	//求出每个节点的dfs序, dfs序对应的节点, 以及每个点所在链的顶端节点
	top[x] = tp, id[x] = ++tot, rk[tot] = x;
	if(son[x]) dfs2(son[x],tp);
	for(int v,i = head[x]; i; i = e[i].next)
		if((v = e[i].to)!=fa[x] && v!=son[x]) dfs2(v,v);
}

inline void pushup(int x){ //基础的线段树向上区间合并
	t[x].sum = (t[t[x].ls].sum+t[t[x].rs].sum)%mod;	 //此题需要将sum和对mod取模
}

void build(int l, int r, int x){ //基础建树,动态开点
	t[x].l = l, t[x].r = r, t[x].lazy = 0;
	if(l == r){
		t[x].sum = val[rk[l]]; return;
	}
	int mid = (l+r)>>1;
	t[x].ls = ++tot, t[x].rs = ++tot;
	build(l,mid,t[x].ls), build(mid+1,r,t[x].rs), pushup(x);
}

inline int len(int x) { return t[x].r-t[x].l+1; }

inline void pushdown(int x){ //基础的线段树标记下放
	if(t[x].lazy && t[x].l != t[x].r){
		int ls = t[x].ls, rs = t[x].rs, lz = t[x].lazy;
		(t[ls].lazy+=lz) %= mod, (t[rs].lazy+=lz) %= mod;
		(t[ls].sum+=lz*len(ls)) %= mod, (t[rs].sum+=lz*len(rs)) %= mod;
		t[x].lazy = 0;
	}
}

void update(int l, int r, int x, int c){ //基础的线段树更新
	if(t[x].l >= l && t[x].r <= r){
		(t[x].lazy += c) %= mod, (t[x].sum += len(x)*c) %= mod;
		return;
	}
	pushdown(x);
	int mid = (t[x].l+t[x].r)>>1;
	if(mid >= l) update(l,r,t[x].ls,c);
	if(mid < r) update(l,r,t[x].rs,c);
	pushup(x);
}

int query(int l, int r, int x){	 //基础的线段树查询
	if(t[x].l >= l && t[x].r <= r) return t[x].sum;
	pushdown(x);
	int mid = (t[x].l+t[x].r)>>1, tp = 0;
	if(mid >= l) tp += query(l,r,t[x].ls);
	if(mid < r) tp += query(l,r,t[x].rs);
	return tp%mod;
}

inline int sum(int x, int y){	//将区间分为多条链,对于每条链直接查询
	int ret = 0;
	while(top[x] != top[y]){ //让x与y到达同一条链
		if(d[top[x]] < d[top[y]]) swap(x,y); //找到更深的点
		(ret += query(id[top[x]],id[x],rt)) %= mod;
		x = fa[top[x]];
	}
	if(id[x] > id[y]) swap(x,y);
	return (ret+query(id[x],id[y],rt))%mod;
}

inline void updates(int x, int y, int c){	//区间加z, 将区间分为多条链
	while(top[x] != top[y]){
		if(d[top[x]] < d[top[y]]) swap(x,y);
		update(id[top[x]],id[x],rt,c);	//对于每条链直接修改
		x = fa[top[x]];
	}
	if(id[x] > id[y]) swap(x,y);
	update(id[x],id[y],rt,c);
}

signed main()
{
	scanf("%lld%lld%lld%lld",&n,&m,&root,&mod);
	rep(i,1,n) scanf("%lld",&val[i]);
	init();
	rep(i,1,n-1){
		int x,y; scanf("%lld%lld",&x,&y);
		add(x,y), add(y,x);
	}
	tot = 0, dfs1(root), dfs2(root, root);
	tot = 0, build(1,n,rt = ++tot);
	rep(i,1,m){
		int op,x,y,k; scanf("%lld",&op);
		if(op == 1){
			scanf("%lld%lld%lld",&x,&y,&k);
			updates(x,y,k);
		}
		else if(op == 2){
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",sum(x,y));
		}
		else if(op == 3){
			scanf("%lld%lld",&x,&y);
			update(id[x],id[x]+size[x]-1,rt,y);
		}
		else{
			scanf("%lld",&x);
			printf("%lld\n",query(id[x],id[x]+size[x]-1,rt));
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/88576338