簡介
主席樹就是可持久化線段樹,它的作用就是不停地訪問某個歷史版本,時間復雜度為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; }
總結
主席樹還是很容易的,只要你看的懂的話,這是本萌新第一次寫博客,有什么建議可以在下面提,或私聊。