1. 程式人生 > 實用技巧 >【模板】輕重鏈剖分

【模板】輕重鏈剖分

目錄

【模板】輕重鏈剖分

題目

傳送門

講解

總的來說,就是一個不難理解,碼量爆炸的東西

推幾篇題解,講得不錯

https://www.luogu.com.cn/blog/zengqinyi/solution-p3384

https://www.cnblogs.com/ivanovcraft/p/9019090.html

前置知識

線段樹(必備),倍增LCA(可以幫助理解,不會應該也可以),鏈式前向星(存圖,不會有人不會吧)

概念

重兒子(子樹結點最多的兒子),重邊(某個點到它的重兒子連成的邊),重鏈(重邊連成的鏈),輕兒子(除重兒子外的其它兒子),輕邊,輕鏈

int n , m , rt , mod;//如題所述,為了習慣,p換位mod
int dat[nn];//輸入的初始值
int dep[nn] , fa[nn] , siz[nn] , hvyson[nn];
//dep[i]:i結點深度,fa[i]:父節點,siz[i]:以i為根的子樹大小,hvyson[i]:i結點的重兒子
int id[nn] , top[nn];
//id[i]:i結點的新標號,等下(第二輪dfs)會講,top[i]:i所在重鏈的頂端結點

第一輪dfs

我們需要處理出這些:

int dep[nn] , fa[nn] , siz[nn] , hvyson[nn];
void dfs1(int x , int fa_ , int deep) {//x:當前結點,fa_:x的父結點,deep:當前深度
	fa[x] = fa_;
	hvyson[x] = 0;
	int maxsiz = 0;
	dep[x] = deep;
	for(int i = head[x] ; i ; i = ed[i].nxt) {//鏈式前向星
		if(ed[i].to == fa_)continue;
		dfs1(ed[i].to , x , deep + 1);
		siz[x] += siz[ed[i].to];
		if(maxsiz < siz[ed[i].to])
			maxsiz = siz[ed[i].to],
			hvyson[x] = ed[i].to;
	}
	++siz[x];
}

第二輪dfs

我們需要處理出這些:

int id[nn] , top[nn];
void dfs2(int x , int chaintop) {//x:同上,chaintop:x所在的重鏈的頂端結點
	static int cnt = 0;
	id[x] = ++cnt;//按照dfs序賦值新編號,以便線段樹操作
	change(root , id[x] , id[x] , dat[x]);//線段樹修改,上傳x原權值到新編號上
	top[x] = chaintop;
	if(hvyson[x] == 0)return;//無兒子
	dfs2(hvyson[x] , chaintop);//優先處理重兒子,下面講
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		int to = ed[i].to;
		if(to == fa[x] || to == hvyson[x])continue;
		dfs2(to , to);
	}
}

核心-查詢&修改

相信很多人都產生了疑問:為什麼要給結點按照dfs序重新編號,並優先處理重兒子呢?

由於是dfs序,那麼以任意一個結點為根,整顆子樹的新編號都是連續的,這就是說,我們可以直接利用線段樹修改或查詢整顆子樹的權值,這就把3,4操作的時間複雜度降到了log級別

由於我們優先處理重兒子,所以同一條重鏈上所有結點的編號都是連續的,這也為線段樹操作提供了方便,以2操作為例(程式碼解釋)

inline int path_query(int x , int y) {//詢問x~y路徑上的點權和
	int res = 0;
	while(top[x] != top[y]) {//x和y不在同一條重鏈上
		if(dep[top[x]] < dep[top[y]])swap_(x , y);//強行讓x所處的重鏈的頂端深度更大
		res += query(root , id[top[x]] , id[x]);//答案累加上從x到top[x]的權值
		res %= mod;
		x = fa[top[x]];//x跳到所處重鏈頂端的父結點
	}
	if(dep[x] > dep[y])swap_(x , y);//此時x,y已經處於同一條重鏈上,強行讓y結點深度更大
	res += query(root , id[x] , id[y]); //答案累加上x~y的點權
	return res % mod;
}

修改操作同理:

inline int path_add(int x , int y , int z) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]])swap_(x , y);
		change(root , id[top[x]] , id[x] , z);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y])swap_(x , y);
	change(root , id[x] , id[y] , z);
}

後記

樹鏈剖分這個東西真的不難,就是很繁瑣,如果想學就真的要沉下心來好好寫程式碼,不要急

程式碼

#include <iostream>
#include <cstdio>
#define nn 100010
#define ll long long
using namespace std;
int read() {
	int re = 0 , sig = 1;
	char c;
	do	if((c = getchar()) == '-')sig = -1; while(c < '0' || c > '9');
	while(c >= '0' && c <= '9')re = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return re * sig;
}
int n , m , rt , mod;
int dat[nn];
int dep[nn] , fa[nn] , siz[nn] , hvyson[nn];
int id[nn] , top[nn];
//SegmentTree-begin======================
struct SegmentTree{
	int ls , rs , l , r;
	ll dat , tag;
}tr[nn * 4];
int root;
int build(int l , int r) {
	static int top = 1;
	int p = top++;
	tr[p].l = l , tr[p].r = r , tr[p].dat = 0 , tr[p].tag = 0;
	if(l == r)	tr[p].ls = tr[p].rs = 0;
	else {
		int mid = (l + r) / 2;
		tr[p].ls = build(l , mid);
		tr[p].rs = build(mid + 1 , r);
	}
	return p;
}
inline void spread(int p) {
	if(tr[p].tag != 0) {
		int ls = tr[p].ls , rs = tr[p].rs;
		tr[ls].dat += (tr[ls].r - tr[ls].l + 1) * tr[p].tag;
		tr[rs].dat += (tr[rs].r - tr[rs].l + 1) * tr[p].tag;
		tr[ls].dat %= mod;	tr[rs].dat %= mod;
		tr[ls].tag += tr[p].tag;
		tr[rs].tag += tr[p].tag;
		tr[ls].tag %= mod;	tr[rs].dat %= mod;
		tr[p].tag = 0;
	}
	return;
}
void change(int p , int l , int r , ll dat) {
	if(l <= tr[p].l && r >= tr[p].r) {
		tr[p].dat += (tr[p].r - tr[p].l + 1) * dat;
		tr[p].tag += dat;
		return;
	}
	spread(p);
	if(l > tr[p].r || r < tr[p].l)return;
	change(tr[p].ls , l , r , dat);
	change(tr[p].rs , l , r , dat);
	tr[p].dat = (tr[tr[p].ls].dat + tr[tr[p].rs].dat) % mod;
} 
int query(int p , int l , int r) {
	if(l <= tr[p].l && r >= tr[p].r)return tr[p].dat;
	if(l > tr[p].r || r < tr[p].l)	return 0;
	spread(p);
	return query(tr[p].ls , l , r) + query(tr[p].rs , l , r);
}
//SegmentTree-end========================
//=======================================
struct ednode{
	int nxt , to;
}ed[nn * 2];
int head[nn];
inline void addedge(int u , int v) {
	static int top = 1;
	ed[top].to = v , ed[top].nxt = head[u] , head[u] = top;
	++top;
}
//=======================================
void dfs1(int x , int fa_ , int deep) {
	fa[x] = fa_;
	hvyson[x] = 0;
	int maxsiz = 0;
	dep[x] = deep;
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		if(ed[i].to == fa_)continue;
		dfs1(ed[i].to , x , deep + 1);
		siz[x] += siz[ed[i].to];
		if(maxsiz < siz[ed[i].to])
			maxsiz = siz[ed[i].to],
			hvyson[x] = ed[i].to;
	}
	++siz[x];
}
void dfs2(int x , int linktop) {
//	cout << x <<' ';
	static int cnt = 0;
	id[x] = ++cnt;
	change(root , id[x] , id[x] , dat[x]);
	top[x] = linktop;
	if(hvyson[x] == 0)return;
	dfs2(hvyson[x] , linktop);
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		int to = ed[i].to;
		if(to == fa[x] || to == hvyson[x])continue;
		dfs2(to , to);
	}
}
//========================================
inline void tree_add(int x , int z) {
	change(root , id[x] , id[x] + siz[x] - 1 , z);
}
inline int tree_query(int x) {
	return query(root , id[x] , id[x] + siz[x] - 1);
}

inline void swap_(int &a , int &b){int tmp=a;a=b;b=tmp;}
inline int path_add(int x , int y , int z) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]])swap_(x , y);
		change(root , id[top[x]] , id[x] , z);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y])swap_(x , y);
	change(root , id[x] , id[y] , z);
}
inline int path_query(int x , int y) {
	int res = 0;
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]])swap_(x , y);
		res += query(root , id[top[x]] , id[x]);
		res %= mod;
		x = fa[top[x]];
	}
	if(dep[x] > dep[y])swap_(x , y);
	res += query(root , id[x] , id[y]); 
	return res % mod;
}
int main() {
	n = read() , m = read() , rt = read() , mod = read();
	for(int i = 1 ; i <= n ; i++)
		dat[i] = read();
	for(int i = 1 ; i < n ; i++) {
		int u = read() , v = read();
		addedge(u , v);
		addedge(v , u);
	}
	root = build(1 , n);
	dfs1(rt , 0 , 0);
	dfs2(rt , rt);
	
	while(m--) {
		int op = read();
		int x , y , z;
		switch(op) {
			case 1 :
				x = read() , y = read() , z = read();
				path_add(x , y , z);
				break;
			case 2 :
				x = read() , y = read();
				printf("%d\n" , path_query(x , y) % mod);
				break;
			case 3 :
				x = read() , z = read();
				tree_add(x , z);
				break;
			case 4 :
				x =  read();
				printf("%d\n" , tree_query(x) % mod);
				break;
		}
	}
	
	return 0;
}