主席樹入門 可持久化線段樹

2021-09-29 06:52:07 字數 4052 閱讀 2078

主席樹的發明,源於權值線段樹,所以我們先來看看權值線段樹。

該篇部落格以poj2104為例,逐步詳解。

2104大致題意:n個數,m個詢問。每個詢問會給一組[l,r,k],求區間(l,r)之間的第k小值。

在上面的例子中,假設我們只涉及乙個區間的修改以及查詢,那麼我們就可以建立乙個權值線段樹。權值線段樹是記錄權值的線段樹。記錄權值指的是,每個節點上存的是區間內的數字出現的總次數。

比如乙個長度為4的陣列[1,2,3,3]的權值線段樹如圖所示:

其中,1和2各出現了一次,3出現了2次,所以權值線段樹的葉子節點記錄的是每個數字出現的次數。若想知道第k小,那麼log(n)的複雜度即可實現,因為線段樹修改以及查詢只涉及該點所在的樹枝。若我想求此例中的第2小,那麼先找樹的根節點為4,表示[1,4]有4個數,繼續查詢左子樹,左孩子的值為2,那麼第二小必在左子樹上,繼續向下查詢。查詢左子樹的右葉子節點即可。這就是乙個區間的查詢方法。

如果查詢某一區間內的呢?我們剛剛舉的例子只能實現從頭開始的區間,並不能實現(l,r)區間內的第k大。類似於求字首和,我們儲存所有的歷史版本,用sum[r]-sum[l-1]。這就是可持續化線段樹。我們可以每輸入乙個a[i],就構造一棵儲存從a[1]到a[i]的權值線段樹。這樣我們就可以把第j棵樹和第(i-1)棵樹上的權值相減,得到一顆嶄新的權值線段樹,可二分查詢求出(i,j)區間上第k大。看大佬的**時學到了stl的lower_bound函式。

這是一次查詢,涉及到多次查詢時這個方法就會超記憶體。眾所周知線段樹開陣列時要開原陣列的4倍,那麼對於2104,我們對每個區間建立乙個線段樹求解,明顯會超記憶體,這時就出現了主席樹。

在剛剛對每乙個區間建立線段樹的時候我們會發現,每新增乙個新的數,只會對該數所在的節點分支產生影響,即只有log(n)的節點改變了,其他的還是照常不變,這樣就會浪費很多空間,於是我們抹去相同節點,只保留改變了的節點,那麼實際的開銷只有n*(4+logn),滿足空間要求。

仔細觀察2104的資料範圍後,我們如果單單按照數字大小來建樹的話,會浪費很多空間,於是大佬們引入了乙個叫離散化的東西。

有關離散化:大佬說,離散化是一種對映關係。對於2104,我們的方法就是把n個數排序,最小的對映為1,次小的對映為2,以此類推(大佬說還要記得去重),比如,離散化後的陣列為。

void

discret

(int b)

更新:

void

update

(int l,

int r,

int&x,

int y,

int pos)

if(pos<=mid)

else

}

然後就是2104的ac**

#include

#include

#include

using

namespace std;

const

int maxn =

1e5+5;

struct hjtreetree[maxn*50]

;//root

int n, m, x, y, k, a[maxn]

, b[maxn]

, root[maxn]

, cnt, res;

intbinary

(int x)

//更新,x為構造的新線段樹,y為上一棵線段樹增加的位置

void

update

(int l,

int r,

int&x,

int y,

int pos)

if(pos<=mid)

else

}//查詢

intquery

(int l,

int r,

int x,

int y,

int k)

int c = tree[tree[y]

.l].num - tree[tree[x]

.l].num;

//核心,這句很重要

int mid =

(l+r)

>>1;

if(c>=k)

else

}void

discret

(int b)

intmain()

//離散化

discret

(b);

for(

int i=

1;i<=n;i++

)for

(int i=

1;i<=m;i++

)return0;

}

題目大意:給定乙個0到n-1的數字組成的序列,然後把第乙個數字放到末尾,得到乙個新的序列,再求反轉數,再把新序列的第乙個數字放到末尾,一直這樣做,求最小逆序數的個數。

思路:

首先用權值線段樹求出當前序列的逆序數對數。然後把第乙個數放到最後乙個,計算貢獻:即當前能組成的逆序數對為後面比它小的數(a[i]個),把它放入最後時,貢獻的逆序數對數為:(n-a[i]+1)即他前面比他小的元素個數。所以最後結果為:n-a[i]+1-a[i]。

#include

using

namespace std;

typedef

long

long

int ll;

const

int n=

5000+10

;struct treenodetree[n*4]

;void

buildtree

(int k,

int l,

int r)

int mid=

(l+r)

>>1;

buildtree

(k<<

1,l,mid)

;buildtree

(k<<1|

1,mid+

1,r);}

void

update

(int k,

int x)

int mid=

(tree[k]

.l+tree[k]

.r)>>1;

if(mid>=x)

update

(k<<

1,x)

;else

update

(k<<1|

1,x)

; tree[k]

.sum=tree[k<<1]

.sum+tree[k<<1|

1].sum;

return;}

intquery

(int k,

int l,

int r)

int mid=

(tree[k]

.l+tree[k]

.r)>>1;

if(mid>=r)

return

query

(k<<

1,l,r)

;else

if(mid

return

query

(k<<1|

1,l,r)

;else

return

query

(k<<

1,l,mid)

+query

(k<<1|

1,mid+

1,r);}

int a[n]

;int

main()

int sum=ans;

for(

int i=

1;i<=n;i++

)printf

("%d\n"

,sum);}

return0;

}

主席樹(可持久化線段樹)

我真弱。連主席樹都不會。主席樹相當於多個線段樹,由於相鄰兩棵線段樹的節點的值只有少許不同,因此可以對於和前一棵樹一樣的子樹乙個指標指過去,無需操作,這樣每棵樹o logn 總複雜度o nlogn 以下是區間k大 include include include define n 100005 defi...

主席樹 可持久化線段樹

首先要學會普通的線段樹,然後理解權值線段樹,而主席樹就是多個權值線段樹 我自己的理解 但是這多個權值線段樹之間有公共部分,節約了空間。它一開始是乙個空樹,後來逐個添數,記錄新增的這個數在那個範圍內,並 1,顯然它每次只更新了一條鏈,其他不需要變,這樣就有了多個版本的線段樹。如果求 l,r 範圍內第k...

可持久化線段樹(主席樹)

qwq我大概又是機房最後乙個學主席樹的了吧 其實之前一直都在講 只是沒做題 做了幾道以後發現都是乙個套路qwq關鍵就是能不能看出來要用主席樹 主要可以解決 靜態 動態區間第k大 樹上也可以 一些有關區間的帶某些限制的詢問 如出現次數等 先把模板粘上來 include include include ...