簡介
主席樹就是可持久化線段樹,它的作用就是不停地訪問某個歷史版本,時間復雜度為O((n+m)logn)。
題目
洛谷3919(https://www.luogu.com.cn/problem/P3919)
如題,你需要維護這樣的一個長度為 N 的數組,支持如下幾種操作
-
在某個歷史版本上修改某一個位置上的值
-
訪問某個歷史版本上的某一位置的值
此外,每進行一次操作(對於操作2,即為生成一個完全一樣的版本,不作任何改動),就會生成一個新的版本。版本編號即為當前操作的編號(從1開始編號,版本0表示初始狀態數組)
分析
先看看暴力做法:每次單點修改一個節點(數組模擬比線段樹少一只log),然后存入一個新的版本里。時間復雜度為O(nm),空間O(nm)。
這顯然在洛谷你會看見一堆黑色(MLE+TLE)
這顯然不可取,那我們有什么更好的做法嗎?
主席樹原理
我們觀察,線段樹的單點修改應該是這樣的,比如我們要修改3號節點

我們發現,其實當我們進行線段樹上單點修改時,只會修改紅色路徑上的點,而修改的點是log(n)個,也就是我們要新建log(n)個節點。

修改如上圖:
主席樹的一些特性:
1、主席樹的根很多很多,且每個根都有一顆完整的線段樹。
2、每一個節點的父親都不止一個。
操作
接下來,一起收菜(It's show time)。
定義
我們要定義一個結構體,這個結構體需要存三個量,權值val,左兒子lson,右兒子rson。
為什么要存左右兒子?
因為我們這是主席樹,要動態開點,已經不是線段樹的左兒子為根*2,右兒子為根*2+1了。
struct tree{
int lson,rson,val;
}t[MAXN*20];
需要注意的是,這里的數組要開N的20倍大小。
建樹
建樹十分簡單,和線段樹基本一樣。注意,這里用了動態開點。
void build(int &rot,int l,int r){//細心的你會發現這個rot是用了取址符的,意味着你只要改了rot,原來你弄進去的變量也會改
rot=++tot;//動態開店
if(l==r){
t[rot].val=n[l];
return;
}
int mid=l+r>>1;
build(t[rot].lson,l,mid);//這里可以直接寫t[rot].lson,因為用了取址符
build(t[rot].rson,mid+1,r);
// t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
修改
修改操作時,我們先把當前節點copy一份,出來的就是上面圖的橙色節點,連的都還是原來的左右兒子。
然后我們判斷我們修改的節點是在左子樹還是在右子樹,然后不停地遞歸下去。
void clone(int &rot,int cl){
rot=++tot;
t[rot]=t[cl];
}
void update(int &rot,int l,int r,int cl,int loc,int value){//這里的rot依然用的是有取址符的
clone(rot,cl);//克隆一份
if(l==r){
t[rot].val=value;
return;
}
int mid=l+r>>1;
if(loc<=mid)update(t[rot].lson,l,mid,t[cl].lson,loc,value);//這里的t[rot].lson和t[cl].lson不要搞錯
if(loc>mid)update(t[rot].rson,mid+1,r,t[cl].rson,loc,value);//同上
// t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
查詢
查詢就很容易了,簡直就是線段樹一樣。
int Find(int rot,int l,int r,int loc){
if(l==r)return t[rot].val;
int mid=l+r>>1;
if(loc<=mid)return Find(t[rot].lson,l,mid,loc);
else return Find(t[rot].rson,mid+1,r,loc);
}
代碼
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000005;
int N,M,n[MAXN],tot,rt[MAXN],v,loc,c,value;
struct tree{
int lson,rson,val;
}t[MAXN*20];
void build(int &rot,int l,int r){
rot=++tot;
if(l==r){
t[rot].val=n[l];
return;
}
int mid=l+r>>1;
build(t[rot].lson,l,mid);
build(t[rot].rson,mid+1,r);
// t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
void clone(int &rot,int cl){
rot=++tot;
t[rot]=t[cl];
}
void update(int &rot,int l,int r,int cl,int loc,int value){
clone(rot,cl);
if(l==r){
t[rot].val=value;
return;
}
int mid=l+r>>1;
if(loc<=mid)update(t[rot].lson,l,mid,t[cl].lson,loc,value);
if(loc>mid)update(t[rot].rson,mid+1,r,t[cl].rson,loc,value);
// t[rot].val=t[t[rot].lson].val+t[t[rot].rson].val;本題不用,本題不用求歷史版本區間和
}
int Find(int rot,int l,int r,int loc){
if(l==r)return t[rot].val;
int mid=l+r>>1;
if(loc<=mid)return Find(t[rot].lson,l,mid,loc);
else return Find(t[rot].rson,mid+1,r,loc);
}
int main(){
scanf("%d%d",&N,&M);
for(int i=1;i<=N;i++)
scanf("%d",&n[i]);
build(rt[0],1,N);
for(int i=1;i<=M;i++){
scanf("%d%d%d",&v,&c,&loc);
if(c==1){
scanf("%d",&value);
update(rt[i],1,N,rt[v],loc,value);
}
if(c==2){
printf("%d\n",Find(rt[v],1,N,loc));
rt[i]=rt[v];
}
}
return 0;
}
總結
主席樹還是很容易的,只要你看的懂的話,這是本萌新第一次寫博客,有什么建議可以在下面提,或私聊。
