前言
首先樹鏈剖分需要使用到線段樹知識,不會線段樹的童鞋請移步至淺談算法——線段樹
在做題中我們會看到一些“在一棵樹上進行路徑修改、求極值、求和”的題,乍一看能夠用線段樹解決,其實僅僅憑線段樹是根本無法完成的。這時候,我們就需要用到一種看起來高級的復雜算法——樹鏈剖分
基本概念
重兒子: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)\)的算法要快
例題
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;
}