树上问题

主要是根据学长的课件来透彻的。所以好多地方直接粘过来了qwq

目录
  • 树链剖分
    • 重链剖分
    • 树剖求LCA
    • 升华
  • 树上差分
  • 各种树上问题

树链剖分

所谓树链剖分,就是将树上的边进行划分。

树链剖分有重链剖分,长链剖分,实链剖分等等。

长链剖分是用来\(O(1)\)\(k\)级祖先的,和优化一些树形DP,具体地来说是一些跟深度有关的DP。

实链剖分是我们常说的LCT(Link-Cut-Tree)。

本文介绍的就是重链剖分。


重链剖分

既然是重链剖分,那么一定有重链和轻链,但是我们怎么来划分轻重链呢?

我们定义:一个节点的所有子节点中\(size\)最大的那个节点为重儿子;那么这两个节点之间所连的边为一条重边。

这样的话我们会得到一些性质。

性质1:从根节点到叶子节点的路径上,跳重链的次数不会超过\(O(logn)\),从叶子节点到根节点,也成立

证明:我们考虑,什么时候会跳重链,一定是当他需要向轻儿子走的时候废话。那么轻儿子的\(size\),一定小于他父亲节点

\(\frac{1}{2}\),那么不断走轻儿子,一直乘\(\frac{1}{2}\),只需要跳\(\log n\)次,就到\(1\)了,也就是叶子节点。

首先考虑如何找到重儿子,我们知道,这个点的size就为他所有而儿子的size之和,且他儿子深度为当前点深度加1,递归的思路已经很明显了qwq。

代码:

int fa[maxn], dep[maxn], size[maxn], height_son[maxn];
vector v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
	size[now] = 1;
	for(int i = 0; i < v[now].size(); i ++){
		int to = v[now][i];
		if(dep[to]) continue;
		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
		fa[to] = now;
		bulid_poutree(to);
		size[now] += size[to];
		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
	}
}

树剖求LCA

我们考虑,如果两个点在同一条重链上,那么肯定是深度小的是LCA。

但是如果不在呢?

我们是不是可以像倍增一下跳(也仅仅是像),跳重链,向上跳,然后一直跳到两个点在同一条重链上,再像刚才一样处理。

让谁跳呢,深度大的?

不不不,是\(top\)深度大的跳。

因为,既然两个点不在同一条重链上,那么显然,他们的\(top\),也不在同一条重链上。

那么类似于倍增,让\(top\)\(fa\),直到跳到同一条重链上。

深度小的是LCA。

如何往上跳,当两个点所在链的最高点不相同时他们一定不在同一条链上,根据这个条件while往上跳即可(让深度深的往上跳)

while(top[x] != top[y]){
	if(dep[top[x]] < dep[top[y]]) swap(x,y);
        /*......*/
	x = fa[top[x]];
}

本人还不透彻,不如去写倍增LCA(其实倍增也不会qaq)


升华

前置芝士:线段树。

先上一道例题来透彻一下树链剖分吧。

好像不只是跳重链那么简单。

在这里,我们引入一个叫做\(dfs\)序的概念。

\(dfs\)序:我们在遍历整棵树时,每个节点被遍历到的时间戳(即这个节点是第几个被遍历到的)。

  1. 那么我们显然可以发现一颗子树内的\(dfs\)序是连续的,而且一条链上的\(dfs\)序也是连续的(可以画图理解)。

  1. 那么我们在询问一条路径时,就可以把这条路径分成好几条链,我们对于每条链分别统计即可。

那么我们怎么才能维护每条链的信息呢?

注意:一条链上的\(dfs\)序是连续的,那么问题转化为,如何维护一个区间的信息?

当然是线段树啊。

什么区间和,区间最值,区间覆盖,线段树简直不能再合适了。

那么是不是这道题就做完了口牙。

那么我们还可以保证我们树链剖分的时间复杂度为\(O(n\log^2n)\)

int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn];
/*其中dfn为该点所对应的dfs序,id为该点的dfs序所对应的自己原来的编号,top为这条重链上最高的点*/
vector v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
	size[now] = 1;
	for(int i = 0; i < v[now].size(); i ++){
		int to = v[now][i];
		if(dep[to]) continue;
		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
		fa[to] = now;
		bulid_poutree(to);
		size[now] += size[to];
		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
	}
}

void dfs(int now, int topfa){ //找到点对应的dfs序
	top[now] = topfa;
	dfn[now] = ++cnt;
	id[cnt] = now;
	if(height_son[now]) dfs(height_son[now], topfa); //先走重链
	for(int i = 0; i < v[now].size(); i ++){//走轻链
	        int to = v[now][i];
		if(fa[now] == to or height_son[now] == to) continue;
		dfs(to,to);
	}
}

找到\(dfs\)序以后我们就把这个问题美滋滋的转化为了区间问题。

对于\(1,2\)操作
首先我们根据1.已经知道对于一颗子树上的\(dfs\)序是连续的,那么我们对子树的操作转到线段树对\(dfn[x]\)\(dfn[x]+size[x]-1\)进行常规的线段树修改和查询操作即可。

对于\(3,4\)操作
而对于一条路径,我们根据2.将其剖为若干条链,每条链上的\(dfs\)序是连续的,那么我们就让深度高的点往上跳,同时记录每一条链或修改每一条链即可,最后不要忘了记录或修改两点在同一条链上的时候而且我们要知道,同一条链上的两个点,深度高的\(dfs\)序大。

代码真的是又臭又难写qaq。

总结一下我提交了20次才A掉这道题时犯的各种错误主要还是我太蔡徐坤了qaq

  1. 如果用链式前向星存图记得开双倍空间,注意他是无向边。

  2. 线段树千万记得开4倍空间开4倍空间4倍空间指针请绕行

  3. 用线段树对我们剖开的树进行维护,在构建树时要从节点1开始,注意你构建的线段树与题中给的树是木有关系的,即这样

tree.bulid_segtree(1,n,1);
  1. 线段树在修改和询问时一定要记得下放标记,并且要记得上传。

  2. 每个人的问题都不一样,还希望大家可以避免掉细节上的错误_(¦3」∠)_

接下来上代码:

#include 
using namespace std;

const int maxn = 1e5 + 10;

int n, m, r, mod, cnt, a[maxn];

struct segpou{
	int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn], head[maxn];
	struct egde{
		int y, nxt;
	}e[maxn*2];
	inline void add(int x, int y){
		e[++cnt].y = y, e[cnt].nxt = head[x];
		head[x] = cnt;
		return;
	}
	void bulid_poutree(int now){//构建树剖所需要的信息
		size[now] = 1;
		for(int i = head[now]; i; i = e[i].nxt){
			int to = e[i].y;
			if(dep[to]) continue;
			dep[to] = dep[now] + 1;
			fa[to] = now;
			bulid_poutree(to);
			size[now] += size[to];
			if(size[to] > size[height_son[now]]) height_son[now] = to;
		}
	}
	void dfs(int now, int topfa){//找到dfs序,且先走重边
		top[now] = topfa;
		dfn[now] = ++cnt;
		id[cnt] = now;
		if(height_son[now]) dfs(height_son[now], topfa);
		for(int i = head[now]; i; i = e[i].nxt){
			int to = e[i].y;
			if(fa[now] == to or height_son[now] == to) continue;
			dfs(to,to);
		}
	}

	#define ls (now << 1)
	#define rs (now<<1|1)
	#define mid ((l+r)>>1)

	struct node{
		int l, r, sum, tag;
		inline int get(){
			return ((sum%mod)+(tag*(r-l+1)%mod))%mod;
		}
	}no[maxn*4];
	void up(int now){
		no[now].sum = ((no[ls].get()%mod) + (no[rs].get()%mod))%mod;
		return;
	}
	void down(int now){
		no[ls].tag += no[now].tag;
		no[rs].tag += no[now].tag;
		no[now].tag = 0;	
		return;
	}
	void bulid_segtree(int l, int r, int now){
		no[now].l = l, no[now].r = r;
		if(l == r) return (void)(no[now].sum = a[id[l]]);
		bulid_segtree(l,mid,ls), bulid_segtree(mid+1,r,rs);
		up(now);
	}
	void chenge(int l, int r, int now, int val){
		if(r < no[now].l or no[now].r < l) return;
		if(l <= no[now].l and no[now].r <= r) return(void)(no[now].tag += val);
		down(now);
		chenge(l,r,ls,val), chenge(l,r,rs,val);
		up(now);
	}
	void query(int l, int r, int now, int &ans){
		if(r < no[now].l or no[now].r < l) return;
		if(l <= no[now].l and no[now].r <= r){
			ans = (ans%mod+no[now].get()%mod)%mod;
			return;
		}
		down(now);
		query(l,r,ls,ans), query(l,r,rs,ans);
		up(now);
	}
	int poutree_query(int x, int y){
        /*对于两个点不在同一条链上时,我们选择让更深的点往上跳,直到在同一条链上为止*/
		int res = 0;
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
			int tmp = 0;
			query(dfn[top[x]],dfn[x],1,tmp);
			res = (res%mod+tmp%mod)%mod;
			x = fa[top[x]];
		}
		if(dep[x] < dep[y]) swap(x,y);
		int tmp = 0;
		query(dfn[y],dfn[x],1,tmp);
		res = (res%mod+tmp%mod)%mod;
		return res;
	}
	void poutree_chenge(int x, int y, int val){
        /*修改也是如此*/
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
			chenge(dfn[top[x]],dfn[x],1,val);
			x = fa[top[x]];
		}
		if(dep[x] < dep[y]) swap(x,y);
		chenge(dfn[y],dfn[x],1,val);
	}
}tree;

signed main(){
	scanf("%d%d%d%d", &n, &m, &r, &mod);
	for(int i = 1; i <= n; i ++){
		scanf("%d", &a[i]);
	}
	for(int i = 1, x, y; i < n; i ++){
		scanf("%d%d", &x, &y);
		tree.add(x,y); tree.add(y,x);
	}
	cnt = 0, tree.dep[r] = 1;
	tree.bulid_poutree(r);
	tree.dfs(r,r);
	tree.bulid_segtree(1,n,1);
	for(int cmp, x, y, z; m; m --){
		scanf("%d", &cmp);
		if(cmp == 1){
			scanf("%d%d%d", &x, &y, &z);
			tree.poutree_chenge(x,y,z);
		}
		if(cmp == 2){
			scanf("%d%d", &x, &y);
			printf("%d\n",tree.poutree_query(x,y));
		}
		if(cmp == 3){
			scanf("%d%d", &x, &y);
			tree.chenge(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,y);
		}
		if(cmp == 4){
			scanf("%d", &x);
			int ans = 0;
			tree.query(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,ans);
			printf("%d\n", ans);
		}
	}
	return 0;
}

树上差分

占坑qwq

各种树上问题

还是占坑( ̄ω ̄;)

你可能感兴趣的:(树上问题)