淺談算法——樹鏈剖分


前言

首先樹鏈剖分需要使用到線段樹知識,不會線段樹的童鞋請移步至淺談算法——線段樹

在做題中我們會看到一些“在一棵樹上進行路徑修改、求極值、求和”的題,乍一看能夠用線段樹解決,其實僅僅憑線段樹是根本無法完成的。這時候,我們就需要用到一種看起來高級的復雜算法——樹鏈剖分

基本概念

重兒子:size[u](表示以u為根節點的子樹節點個數)為v的子節點中最大的,稱u為v的重兒子
輕兒子:v的其他子節點
重邊:v與其重兒子的連邊
輕邊:v與其輕兒子的連邊
重鏈:重邊組成的一條鏈

如圖所示,圓圈內為size,粗邊為重邊,我們稱某條路徑為重鏈,當且僅當它全部由重邊組成。不存在輕鏈這一說法

任意一個點到根的路徑上,不超過\(\log n\)條輕邊,也不超過\(\log n\)條重鏈,這是整個算法時間復雜度的保證,證明略。因為我也不會證明

基本操作

樹鏈:樹上的路徑
剖分:把路徑分為兩類

樹鏈剖分的操作為兩次dfs

  • 找重邊
  • 把重邊連成重鏈

記size[v]表示以v為根的兒子節點的個數,deep[v]表示v的深度(根深度為1),top[v]表示v所在重鏈的頂端節點,fa[v]表示v的父親節點,Rem[v]表示v的重兒子,ID[v]表示v的新編號

第一遍dfs將size,deep,fa,Rem求出來

第二遍dfs的時候,每次優先dfs重兒子,這樣可以使得重兒子的編號連續

如下圖示,粗邊為重邊,細邊為輕邊。節點邊帶紅點的說明其為該條重鏈的頂端,藍色數字代表其新編號

為什么要按這種方式去進行樹鏈剖分?
因為在剖分完之后,我們可以發現重鏈上的點的編號是連續的,所以我們可以用線段樹或其他高級數據結構去維護重鏈上的信息,由於每條重鏈所占據的編號互不相同,所以我們可以把這些重鏈首尾相連,放到一個高級數據結構上進行維護。同時我們認為一個點也屬於一條重鏈。輕邊的信息可以直接維護。因為它不好維護,除非把邊權下移作為點權

樹上基本操作

單點修改:直接按新編號對應單點修改即可
路徑修改
1、u和v在同一條重鏈上,直接線段樹區間修改即可
2、u和v不在同一條重鏈上,我們要想辦法把它們往同一條重鏈上靠

  • fa[top[u]]與v在同一條重鏈上,兩次做區間修改即可
  • u經過若干條重鏈與輕邊后和v在同一條重鏈上,多次做區間修改即可
  • u和v都經過若干條重鏈與輕邊后在同一條重鏈上,那么每次找到深度較深的點x,做一次區間修改后,在將該點跳到fa[top[x]],知道u和v在同一條重鏈上

前兩種情況是特殊的第三種情況。
查詢操作:和路徑修改類似,求極值,最大值最小值等,在線段樹操作時按照具體情況修改即可

由於經過的重鏈個數不會超過\(\log n\)級別,因此整個樹鏈剖分的復雜度為\(O(n\log^2n)\),但實際上比某些\(O(n\log n)\)的算法要快

例題

P3384 【模板】樹鏈剖分

Description
如題,已知一棵包含N個結點的樹(連通且無環),每個節點上包含一個數值,需要支持以下操作:
操作1: 格式: 1 x y z 表示將樹從x到y結點最短路徑上所有節點的值都加上z
操作2: 格式: 2 x y 表示求樹從x到y結點最短路徑上所有節點的值之和
操作3: 格式: 3 x z 表示將以x為根節點的子樹內所有節點值都加上z
操作4: 格式: 4 x 表示求以x為根節點的子樹內所有節點值之和

Input
第一行包含4個正整數N、M、R、P,分別表示樹的結點個數、操作個數、根節點序號和取模數(即所有的輸出結果均對此取模)。
接下來一行包含N個非負整數,分別依次表示各個節點上初始的數值。
接下來N-1行每行包含兩個整數x、y,表示點x和點y之間連有一條邊(保證無環且連通)
接下來M行每行包含若干個正整數,每行表示一個操作,格式如下:
操作1: 1 x y z
操作2: 2 x y
操作3: 3 x z
操作4: 4 x

Output
輸出包含若干行,分別依次表示每個操作2或操作4所得的結果(對P取模

Sample Input
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3

Sample Output
2
21

HNIT
對於100%的數據:$ N \leq {10}^5, M \leq {10}^5$

故輸出應依次為2、21(重要的事情說三遍:記得取模)

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
	int x=0,f=1;char ch=getchar();
	for (;ch<'0'||ch>'9';ch=getchar())	if (ch=='-')    f=-1;
	for (;ch>='0'&&ch<='9';ch=getchar())	x=(x<<1)+(x<<3)+ch-'0';
	return x*f;
}
inline void print(int x){
	if (x>=10)     print(x/10);
	putchar(x%10+'0');
}
const int N=1e5;
int v[N+10],dfn[N+10],Line[N+10];
int n,m,root,mod;
struct Segment{//線段樹Lazy標記操作
	#define ls (p<<1)
	#define rs ((p<<1)|1)
	int tree[(N<<2)+10],Lazy[(N<<2)+10];
	void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
	void add_tag(int p,int l,int r,int v){
		tree[p]=(tree[p]+1ll*(r-l+1)*v)%mod;
		Lazy[p]=(Lazy[p]+v)%mod;
	}
	void pushdown(int p,int l,int r){
		if (!Lazy[p])	return;
		int mid=(l+r)>>1;
		add_tag(ls,l,mid,Lazy[p]),add_tag(rs,mid+1,r,Lazy[p]);
		Lazy[p]=0;
	}
	void build(int p,int l,int r){
		if (l==r){tree[p]=v[Line[l]]%mod;return;}
		int mid=(l+r)>>1;
		build(ls,l,mid),build(rs,mid+1,r);
		updata(p);
	}
	void insert(int p,int l,int r,int x,int y,int v){
		if (x<=l&&r<=y){add_tag(p,l,r,v);return;}
		int mid=(l+r)>>1;
		pushdown(p,l,r);
		if (x<=mid)	insert(ls,l,mid,x,y,v);
		if (y>mid)	insert(rs,mid+1,r,x,y,v);
		updata(p);
	}
	int query(int p,int l,int r,int x,int y){
		if (x<=l&&r<=y)	return tree[p];
		pushdown(p,l,r);
		int mid=(l+r)>>1,res=0;
		if (x<=mid)	res=(res+query(ls,l,mid,x,y))%mod;
		if (y>mid)	res=(res+query(rs,mid+1,r,x,y))%mod;
		return res;
	}
}Tree;
struct Start{
	int pre[(N<<1)+10],child[(N<<1)+10],now[N+10];
	int deep[N+10],size[N+10],fa[N+10],Rem[N+10],top[N+10];
	int tot,cnt;
	void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y;}
	void build(int x,int Deep){//第一遍dfs
		deep[x]=Deep,size[x]=1;
		for (int p=now[x],son=child[p],Max=0;p;p=pre[p],son=child[p]){
			if (son==fa[x])	continue;
			fa[son]=x;
			build(son,Deep+1);
			size[x]+=size[son];
			if (Max<size[son])	Max=size[son],Rem[x]=son;
		}
	}
	void dfs(int x){//第二遍dfs
		if (!x)	return;
		Rem[fa[x]]==x?top[x]=top[fa[x]]:top[x]=x;
		dfn[x]=++cnt,Line[cnt]=x;
		dfs(Rem[x]);
		for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
			if (son==fa[x]||son==Rem[x])	continue;
			dfs(son);
		}
	}
	void insert(int x,int y,int z){//區間操作
		while (top[x]!=top[y]){
			if (deep[top[x]]<deep[top[y]])	swap(x,y);
			Tree.insert(1,1,n,dfn[top[x]],dfn[x],z);
			x=fa[top[x]];
		}
		if (deep[x]>deep[y])	swap(x,y);
		Tree.insert(1,1,n,dfn[x],dfn[y],z);
	}
	int query(int x,int y){//區間查詢
		int res=0;
		while (top[x]!=top[y]){
			if (deep[top[x]]<deep[top[y]])	swap(x,y);
			res=(res+Tree.query(1,1,n,dfn[top[x]],dfn[x]))%mod;
			x=fa[top[x]];
		}
		if (deep[x]>deep[y])	swap(x,y);
		res=(res+Tree.query(1,1,n,dfn[x],dfn[y]))%mod;
		return res;
	}
}T;
int main(){
	n=read(),m=read(),root=read(),mod=read();
	for (int i=1;i<=n;i++)	v[i]=read();
	for (int i=1,x,y;i<n;i++)	x=read(),y=read(),T.join(x,y),T.join(y,x);
	T.build(root,1),T.dfs(root),Tree.build(1,1,n);
	for (int i=1;i<=m;i++){
		int t=read();
		if (t==1){
			int x=read(),y=read(),z=read();
			T.insert(x,y,z);
		}
		if (t==2){
			int x=read(),y=read();
			printf("%d\n",T.query(x,y));
		}
		if (t==3){//子樹修改
			int x=read(),z=read();
			Tree.insert(1,1,n,dfn[x],dfn[x]+T.size[x]-1,z);
		}
		if (t==4){//子樹查詢
			int x=read();
			printf("%d\n",Tree.query(1,1,n,dfn[x],dfn[x]+T.size[x]-1));
		}
	}
	return 0;
}


免責聲明!

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



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