學的時候比較朦朧,現在不朦朧了,所以寫一下
講解
重兒子:一個節點的子樹大小最大的兒子
輕兒子:非重兒子
重鏈:節點 -> 重兒子 -> 重兒子 .. 這樣的鏈
A beautiful Tree
藍線為重鏈
可以發現,樹上的所有節點一定屬於且僅屬於一個重鏈
首先要知道如何找重鏈
這很簡單,可以透過一遍 DFS 得到:
void dfs(int now){
size[now]=1;
int maxsonsize=0;
for(i:遍歷所有子節點){
dfs(i)
if(size[i]>maxsonsize){
maxson[now]=i;
maxsonsize=size[i]
}
size[now]+=size[i]
}
}
其中 size
是節點的子樹大小
為什麼一定要剖出重鏈來?因為我們要進行的是在鏈上跳躍的操作,而我們可以跳躍的範圍是一整條鏈,因此鏈越長,對複雜度就越有利,而且我們將不同的重鏈剖出來,還能保證每一個節點都在一條重鏈上,不重不漏
找出重兒子以後怎麼找重鏈呢
這個就更簡單了,我們再做一遍 DFS,記錄每個鏈頂端的節點,然後將其賦給鏈中的每一個節點(或者你在這裡開個 cnt 也是可以的,只要能起到區分的作用就行),這樣,值相同的節點就一定在同一條鏈裡了
void dfs2(int now,int topnode){
top[now]=topnode;
if(maxson[now]==0) return; //沒有兒子就返回
dfs2(maxson[now],top_node) //搜尋重兒子,此時不改變鏈
for(i:遍歷子節點){
if(i!=maxson[now]){
dfs2(i,i); //輕兒子的重鏈頂端就是這個輕兒子,可以看上面的圖
} //如果你在這裡寫 cnt 的話就是 ++cnt
}
}
實際上我們還需要在這兩遍 DFS 中維護一些資訊,具體的資訊列在下面:
DFS1
- 節點父親
- 節點深度
- 節點子樹大小
- 節點的重兒子編號
DFS2
- 構建鏈
- 按遍歷順序為每個節點分配新編號
- 將原節點權值遷移到新編號
可以寫出下面兩個程式碼:
void dfs1(int now,int last){
fa[now]=last;
deep[now]=deep[last]+1;
size[now]=1;
int maxsonsize=0;
for(int i:e[now]){
if(i!=last){
dfs1(i,now);
if(size[i]>maxsonsize){
maxson[now]=i;
maxsonsize=size[i];
}
size[now]+=size[i];
}
}
}
void dfs2(int now,int nowtop,int last){
id[now]=++cnt;
wnew[id[now]]=w[now];
top[now]=nowtop;
if(!maxson[now]) return;
dfs2(maxson[now],nowtop,now);
for(int i:e[now]){
if(i!=last and i!=maxson[now]){
dfs2(i,i,now);
}
}
}
這裡我們給每個節點都分配了新的編號,那麼有什麼用嗎
因為我們這麼分配編號有兩個非常好的性質
-
同一個重鏈上的點,編號總是連續的,並且上面的節點編號總是比下面的節點編號要小
-
同一個子樹中的點,編號是一個連續區間,並且這個區間總是 \([id_r,id_r+size-1]\)(\(r\) 是子樹根節點)
但是需要注意的是,為了實現這兩個非常好的性質,我們需要在 DFS2 中優先遍歷重兒子,因為重兒子和當前節點在一條鏈中,只有優先遍歷了重兒子,才能保證按遍歷順序分配的編號是連續的
那麼有了這兩個非常好的性質,我們可以幹什麼呢
- 查詢路徑資訊
假如有一道題讓我們查詢樹上 \((x,y)\) 的簡單路徑權值和(點權)
那麼我們可以考慮這樣降低複雜度:
- 如果 \(x,y\) 不在一條鏈上,將其中鏈頂深度較小的那個節點跳到它所在的鏈頂,同時統計該節點到其頂端的答案
- 重複如上操作,直到 \(x,y\) 在一條鏈上
- 此時直接統計即可
以上操作中,由於我們只在同一條鏈上跳,因此編號總是連續的,所以可以用資料結構來維護
下面是一份線段樹維護的查詢
int ask_path_sum(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
res+=ask_sum(1,id[top[x]],id[x]);
x=fa[top[x]];
}
if(deep[x]<deep[y]) swap(x,y);
res+=ask_sum(1,id[y],id[x]);
return res;
}
路徑修改同理
然後考慮怎麼用第二個性質
第二個性質也非常好,可以用來作子樹整體修改/查詢
int ask_subtree(int x){
return stree::ask_sum(1,id[x],id[x]+size[x]-1);
}
例題
樹的統計
- 單點修改
- 路徑和查詢
- 路徑最值查詢
這兩個資訊都能用線段樹來維護
單點修改總是簡單的,直接線上段樹上定位即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
int deep[200001],fa[200001],size[200001],maxson[200001];
vector<int>e[200001];
void dfs1(int now,int last){
fa[now]=last;
deep[now]=deep[last]+1;
size[now]=1;
int maxsonsize=0;
for(int i:e[now]){
if(i!=last){
dfs1(i,now);
if(size[i]>maxsonsize){
maxson[now]=i;
maxsonsize=size[i];
}
size[now]+=size[i];
}
}
}
int w[200001];
int id[200001],top[200001],wnew[200001];
int cnt=0;
void dfs2(int now,int nowtop,int last){
id[now]=++cnt;
wnew[id[now]]=w[now];
top[now]=nowtop;
if(!maxson[now]) return;
dfs2(maxson[now],nowtop,now);
for(int i:e[now]){
if(i!=last and i!=maxson[now]){
dfs2(i,i,now);
}
}
}
namespace stree{
struct tree{
int l,r;
int sum,max;
}t[800001];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void build(int id,int l,int r){
t[id].l=l;t[id].r=r;
if(l==r){
t[id].sum=wnew[l];
t[id].max=wnew[l];
return;
}
int mid(l,r);
build(tol,l,mid);
build(tor,mid+1,r);
t[id].sum=(t[tol].sum+t[tor].sum);
t[id].max=max(t[tol].max,t[tor].max);
}
int ask_sum(int id,int l,int r){
if(l>r) swap(l,r);
if(l<=t[id].l and t[id].r<=r){
return t[id].sum;
}
pushdown(id);
if(r<=t[tol].r) return ask_sum(tol,l,r);
else if(l>=t[tor].l) return ask_sum(tor,l,r);
else{
return (ask_sum(tol,l,t[tol].r)+ask_sum(tor,t[tor].l,r));
}
}
int ask_max(int id,int l,int r){
if(l>r) swap(l,r);
if(l<=t[id].l and t[id].r<=r){
return t[id].max;
}
pushdown(id);
if(r<=t[tol].r) return ask_max(tol,l,r);
else if(l>=t[tor].l) return ask_max(tor,l,r);
else{
return max(ask_max(tol,l,t[tol].r),ask_max(tor,t[tor].l,r));
}
}
}
int ask_path_max(int x,int y){
int res=-1;
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
res=max(res,stree::ask_max(1,id[top[x]],id[x]));
x=fa[top[x]];
}
if(deep[x]<deep[y]) swap(x,y);
res=max(res,stree::ask_max(1,id[y],id[x]));
return res;
}
int ask_path_sum(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
res+=stree::ask_sum(1,id[top[x]],id[x]);
x=fa[top[x]];
}
if(deep[x]<deep[y]) swap(x,y);
res+=stree::ask_sum(1,id[y],id[x]);
return res;
}
int n,m;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n-1;++i){
int x,y;
scanf("%lld %lld",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
for(int i=1;i<=n;++i){
scanf("%lld",&w[i]);
}
scanf("%lld",&m);
dfs1(1,0);
dfs2(1,1,0);
stree::build(1,1,n);
while(m--){
string op;int x,y,z;cin>>op;
if(op[0]=='C'){
scanf("%lld %lld",&x,&z);
stree::change(1,id[x],id[x],z-stree::ask_sum(1,id[x],id[x]));
}
if(op[0]=='Q' and op[1]=='M'){
scanf("%lld %lld",&x,&y);
printf("%lld\n",ask_path_max(x,y));
}
if(op[0]=='Q' and op[1]=='S'){
scanf("%lld %lld",&x,&y);
printf("%lld\n",ask_path_sum(x,y));
}
}
}