主席樹學習筆記

2021-07-09 07:41:37 字數 2629 閱讀 6337

問題:給定乙個n個數的序列,q次詢問第x個數到第y個數中的第k最值。我們假定是第k小。

為了使討論更加簡便,我們假定序列的每個數都是不大於n的正整數。當然一般題目中元素範圍很大,但是可以用離散化預處理來做到這一點。

考慮乙個比較高階的做法:令g[i][j]為前i個數中,值為j的數的個數。很容易用o(n^2)的代價獲得g[ ][ ]陣列和其字首和陣列f[ ][ ],其中f[i][j]表示前i個數中,不大於j的數的個數。

注意到第l個數到第r個數中不大於t的數共有f[r][t]-f[l-1][t]個。因此對於每個詢問,我們可以二分答案m,比較f[y][m]-f[x-1][m]和k的大小關係。

這個演算法比完全暴力要更優,綜合時間複雜度為o(n^2+qlogn),可以接受q較大的情況,**如下:

#include

#include

#define rpt(i,l,r) for(i=l;i<=r;i++)

#define n 2015

int a[n];

int p[n][n];

int n,i,j,q,x,y,k,l,r,m,ans;

int main()

scanf("%d",&q);

while(q--)

printf("%d\n",ans);}}

但是這個演算法也有缺陷,無法支援n較大的情況。

主席樹,全名可持久化線段樹。先不要管可持久化是啥,先看線段樹。

我學習的時候因為這個迷惑了很久,準確地說,這裡是權值線段樹。

我們知道,一般的線段樹用於維護乙個序列a[1...n]的諸多區間問題。在權值線段樹中,a[i]表示i這個值出現的次數。

權值線段樹一些最基礎的應用就是給乙個可重數集新增數,刪除數,詢問第k小,詢問排名,等等。

我們先把上文提到的演算法中的f陣列作一些小變換:因為每個g[i]都是乙個一維陣列,我們可以將對於每個i,g[i][1...n]這些元素建立一棵線段樹。

看上去非常複雜,舉個例子畫個圖冷靜一下。設n=4,序列為2,4,1,3,則包括g[0][ ]的g[ ][ ]陣列為:

j=1j=2

j=3j=4

i=1010

0i=201

01i=3110

1i=411

11對每行都建一棵線段樹,第i棵的結點資訊記錄第i行的某區間和,這個和值也就對應著序列前i個數中值屬於某值域的數的個數:

第一棵:

1[1...4]

1[1...2]           0[3...4]

0[1]     1[2]     0[3]     0[4]

第二棵:

2[1...4]

1[1...2]           1[3...4]

0[1]     1[2]     0[3]     1[4]

第三棵:

3[1...4]

2[1...2]           1[3...4]

1[1]     1[2]     0[3]     1[4]

第四棵:

4[1...4]

2[1...2]           2[3...4]

1[1]     1[2]     1[3]     1[4]

我們還是可以秉著二分思想用與前一種演算法幾乎相同的做法來求取答案,注意到這裡二分答案可能會詢問到的任何乙個值域都一定是線段樹的某一整段,因此判斷答案是過小還是過大可以做到o(1),因此對於每個詢問的複雜度都是o(logn),綜合複雜度此時還是o(n^2+qlogn)。

我們發現,這種二分答案做似乎總有乙個瓶頸:要統計前若干個數中出現在某值域內的次數,似乎總是需要o(n^2)。真的是這樣嗎?

我們觀察第

一、二棵線段樹,發現它們的左子樹完全相同。不僅如此,它們的右子樹的左子樹也是完全相同的。第

二、三棵,第

三、四棵,甚至是第零、一棵,其中第零棵為結點資訊的和值均為0的線段樹,都滿足只有一條從根到某葉子的鏈上的各點有區別。顯然,不論n有多大,相鄰兩個線段樹中,只有某一條鏈上的值不同。而線段樹的深度當然一定是logn級別的,因此每棵樹實際上只有logn個結點是不與前面重複的,既然別的都是重複的,那我們是否可以對每棵樹只儲存logn級別的結點呢?當然可以!

甚至可以這樣:

類似這樣構造的每棵線段樹,即能做到o(nlogn)的代價。因此綜合複雜度變為o(nlogn+qlogn),非常優越,**如下:

#include

#include

#define rpt(i,l,r) for(i=l;i<=r;i++)

#define n 100005

int n,cnt,i,a,q,x,y,k;

struct nodet[n*20];

void build(int x,int l,int r)

void ins(int x,int xx,int y)

else

}int query(int x,int xx,int k)

int main()

scanf("%d",&q);

while(q--)

}

主席樹 學習筆記

主席樹就是權值線段樹的乙個集合 模板題為求某個區間的第 k 大,權值線段樹也有求第 k 大這個功能,但是不能維護區間,只能求整個全域性第 k 大 所以學習這個之前,務必先搞懂權值線段樹 所以 都是這道題的 模板 可持久化線段樹 1 主席樹 1 先從建樹開始說起 int build int l,int...

主席樹學習筆記

學習博文 主席樹總結 p3834 模板 可持久化線段樹 2 主席樹 給出乙個序列,每次詢問給定區間內第k小的值。主席樹模板。考慮最簡單的情況,也就是查詢區間固定。首先對資料進行離散化,用線段樹維護。每個節點對應離散化後值域的數的總個數 size.從上到下進行查詢時,判斷當前節點左子樹的 size 和...

主席樹 學習筆記

對於詢問 1,n 的第 k 小數,我們都知道直接上權值線段樹就行了。那麼對於任意區間的第 k 小數呢?暴力一點,每次開一顆線段樹。空間肯定 那麼此時,主席樹便應運而生。主席樹的主要思想就是 保留每次插入操作時的歷史版本,以便查詢區間第 k 小的數。先說流程。1.先建一顆空的權值線段樹,1,len 2...