樹上問題


主要是根據學長的課件來透徹的。所以好多地方直接粘過來了qwq

樹鏈剖分

所謂樹鏈剖分,就是將樹上的邊進行划分。

樹鏈剖分有重鏈剖分,長鏈剖分,實鏈剖分等等。

長鏈剖分是用來\(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<int> 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<int> 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\)操作
對於一條路徑,我們根據2.將其剖為若干條鏈,每條鏈上的\(dfs\)序是連續的,那么我們就讓深度高的點往上跳,同時記錄每一條鏈或修改每一條鏈即可,最后不要忘了記錄或修改兩點在同一條鏈上的時候,而且我們要知道,同一條鏈上的兩個點,深度高的\(dfs\)序大。

對於\(3,4\)操作
首先我們根據1.已經知道對於一顆子樹上的\(dfs\)序是連續的,那么我們對子樹的操作轉到線段樹上對\(dfn[x]\)\(dfn[x]+size[x]-1\)進行常規的線段樹修改和查詢操作即可。

代碼真的是又臭又難寫qaq。

總結一下我提交了20次才A掉這道題時犯的各種錯誤主要還是我太蔡徐坤了qaq

  1. 如果用鏈式前向星存圖記得開雙倍空間,注意他是無向邊。

  2. 線段樹千萬記得開4倍空間開4倍空間4倍空間指針請繞行

  3. 用線段樹對我們剖開的樹進行維護,在構建樹時要從節點1開始,注意你構建的線段樹與題中給的樹是木有關系的,即這樣

tree.bulid_segtree(1,n,1);
  1. 線段樹在修改和詢問時一定要記得下放標記,並且要記得上傳。

  2. 每個人的問題都不一樣,還希望大家可以避免掉細節上的錯誤_(¦3」∠)_

接下來上代碼:

#include <bits/stdc++.h>
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

各種樹上問題

P3398 倉鼠找sugar

P1967 貨車運輸


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM