靜態主席樹學習總結(詳細)

2021-09-20 12:54:49 字數 3465 閱讀 9483

(菜雞這兩天學了靜態主席樹,看了好多大佬的**和解釋= =,終於看懂了!在此寫乙個菜雞詳細版的靜態主席樹總結o(∩_∩)o~~)

主席樹也稱函式式線段樹也稱可持久化線段樹。

求任意區間[l,r]的第k大數。假設這個區間是[1,r]:

(1)我們若每次求區間[1,r]的第k大數,則用權值線段樹可求解,即從根節點開始向下遞迴,若左子節點的sum值》=k,可見第k大的值在左子樹里,向左遞迴;反之,向右遞迴,直到當前區間l==r時,l即為所求值。

比如,插入數:1 2 2 4 3 5 ,求第3大的數(圖中區間旁標的是線段樹的sum值)

(2)若是求任意區間的該怎麼辦呢,靜態主席樹登場~~~

所謂主席樹呢,就是對原來的數列[1..n]的每乙個字首[1..i](1≤i≤n)建立一棵線段樹,線段樹的每乙個節點存某個字首[1..i]中屬於區間[l..r]的數一共有多少個,如下圖:

比如,插入數:1 10 4 2

(1)去重,離散化:1 2 3 4,相當於依次插入點1 4 3 2

(2)對於數列1到4,構建5棵字首線段樹,如圖:

rt[i]表示第i棵樹的根節點編號;

紅色圈圈表示節點編號;

綠色筆標註的是個節點sum值;

鉛筆箭頭表示更新的情況;

(3)查詢,若要查詢[i..j]中第k大數時,則讓第j棵樹,減第i-1棵樹,此時的得到的新樹,記錄的正好是區間[l,r]的情況,在得到的樹中查詢第k大的數即可,

比如說,區間[2,4]中第2大,則樹4減樹1得到:

[1,4](3)

[1,2] (1)                   [2,4](2)

[1,1]       [2,2] (1)      [3,3](1)        [4,4](1)

在這棵樹上查詢第2的的數,為3

(4)存在問題:對每乙個字首都建一棵樹,會mle,觀察到每個[1..i]和[1..i-1]只有一條路是不一樣的(鉛筆標出的更新的路徑),那麼其他的結點只要用回前一棵樹的結點即可,時空複雜度為o(nlogn)。

那麼,每棵樹都重複利用前一棵樹的節點,可達到節省節點(節省空間的)效果,當然,每棵樹根節點編號會發生改變:

知道了原理,**就好說多了~

(1)輸入陣列,去重,離散化(不多說)

for(int i=1;i<=n;i++)

sort(a+1,a+1+n);

int nn=unique(a+1,a+1+n)-(a+1);

(2)初始化第0棵樹

通過上面的「原理部分」的圖,大家可能發現了,主席樹在建樹的時候,和普通線段樹的建樹操作不太一樣。

對於普通線段樹:根節點的編號是x,左子樹的編號是x<<1,右子樹是x<<1|1;

而對於主席樹,由於多個節點重複使用,很顯然不滿足這個性質,所以,要用rt[i],ls[rt[i]],rs[rt[i]]分別存,樹i的根節點編號,左子樹根節點編號,右子樹根節點編號。

我們用遞迴的方法,根節點編號為x,則左子樹根節點為x+1,

再以左子樹為根節點,遞迴其左子樹,直到左子樹為空,再去遞迴右子樹,(相當於先序遍歷,可結合上圖理解一下主席樹編號是怎麼賦值的)

rt[i]:=樹i的根節點編號是多少

ls[rt[i]]:=根節點編號為rt[i]的左子樹的根節點編號

rs[rt[i]]:=根節點編號為rt[i]的右子樹的根節點編號

//build(rt[0],1,nn);

void build(int &o,int l,int r)

(3)從1到n建樹操作

由「原理」部分可知,插入i節點的時候,建第i棵樹,是在第i-1棵樹的基礎上建的,

先把新樹,左子樹的 ls 和 rs 都賦值成老樹的 ls 和 rs,當然,其中乙個必定會改變(每次更新只改變一條路徑嘛)

更新的點p在其左子樹中時,左節點肯定要變啦,右節點不變,直接用之前的節點就好啦!(rs[o]=rs[pre])

此時update(ls[o],l,m,ls[pre],p);

改變左子樹的ls的值,和sum值

/*

for(int i=1;i<=n;i++)

*/void update(int &o,int l,int r,int pre,int p)

(4)查詢

假設查詢區間[l,r],則讓第r棵樹-第(l-1)棵樹,在得到的這棵新樹上查詢就好啦

第r棵樹根節點編號:rr=rt[r]

第r棵樹左子樹根節點的編號:ls[rr]

第r棵樹左子樹的sum值:sum[ls[rr]]

同理,第l-1棵樹,左子樹的sum值:sum[ls[lr]]

cnt=sum[ls[rr]]-sum[ls[lr]] := 第r棵樹-第(l-1)棵樹的左子樹的sum值

若k<=cnt,查詢左子樹,

反之,查詢右子樹

//int ans=query(rt[l-1],rt[r],1,nn,k);

int query(int lr,int rr,int l,int r,int k)

完結撒花✿✿ヽ(°▽°)ノ✿

看一道模板題:

洛谷p3834

**如下:

#include#include#include#include#include#include#include#include#include#includeusing namespace std;

#define ll long long

typedef pairp;

const int inf=0x3f3f3f3f;

const int n=200005;

int a[n],b[n],tot=0;

int rt[n],ls[n*20],rs[n*20],sum[n*20];

void build(int &o,int l,int r)

void update(int &o,int l,int r,int pre,int p)

int query(int lr,int rr,int l,int r,int k)

int main()

sort(a+1,a+1+n);

int nn=unique(a+1,a+1+n)-(a+1);

int l,r,k;

build(rt[0],1,nn);

for(int i=1;i<=n;i++)

while(m--)

}

學習總結 主席樹

突然想起主席樹。依稀只記得演算法的大概了,所以今天又拿出來溫習了一下,畢竟學習演算法是很快的,忘掉也是很快的,而演算法一定是要運用的,否則也就沒有存在的意義了。首先主席樹是一棵線段樹,而且是乙個字首權值線段樹,支援靜態的查詢。這個演算法最大的亮點就是在原有的一棵樹的基礎上,如果想將乙個元素插入,就根...

靜態主席樹

首先主席樹解決什麼樣的問題?最經典的問題就是 區間第k小問題 也就是指定乙個區間,要求求出這個區間中第k小的數字 在搞懂什麼是主席樹之前,我們要先對權值線段樹有一定的了解,下面我們就先說一下權值線段樹,然後詳細說一下主席樹以及主席樹程式的實現.權值線段樹 每個葉子節點的數值表示的是 陣列中含有這個數...

靜態主席樹

主席樹 對於序列的每乙個字首建一棵以序列裡的值為下標的線段樹 所以要先離散化 記錄該字首序列裡出現的值的次數 記離散後的標記為1 n 下面值直接用1 n代替 對於區間 x,y 的第k大的值,那麼從root x 1 root y 開始,t root y 1,mid root x 1 1,mid t表示...