poj 2104 無修改主席樹

2022-08-13 10:15:15 字數 2840 閱讀 6134

題目大意:

求序列的區間第k大

基本思路:

下面闡述我所理解的主席樹的基本思路和細節:

**:這種求區間第k(大)小的題目

最容易想到的做法就是對於每個詢問,對[l, r]區間排個序,輸出第k小,這樣的複雜度是o(m×n

logn

'>m×nlogn

m×nlogn)

大家都很容易想到排序,但是對於每個詢問每個區間排序的代價太大了...

再想想,讓我們加入一些線段樹的思想,

要求第k小,也就是與個數相關,那麼我們可以 以[l,r]區間內的數的個數來建立一棵線段樹

結點的值是數的個數,當我們要找第k小的數時,若左子樹大於k,那麼很顯然第k小的數在左子樹中;若左子樹小於k,那麼第k小的數在右子樹中

建樹的複雜度是o(nlogn),查詢的複雜度是o(logn)      (這裡的n是不相同數的數量)

若我們仍對每個查詢建樹,那麼複雜度絲毫沒有降低(反而提高了),那有沒有什麼辦法可以不要每次查詢都建樹呢?

(讓我們聯想一下字首和) 假設我們知道[1, l-1]之間有多少個數比第k小的數小,那麼我們只要減去這些數之後在[1, r]區間內第k小的數即是[l, r]區間內的第k小數

更確切的說,我們要求[l, r]區間內的第k小數  可以 用以[1, r]建立的線段樹去減去以[1, l-1] 建立的線段樹

這樣能夠減的條件是這兩棵樹必須是同構的。

若是不太明白, 我們來舉個例子:

如有序列  1 2 5 1 3 2 2 5 1 2

我們要求 [5,10]第5小的數

(數列中不存在4、6、7、8 但根據原理就都寫出來了,為方便理解,去掉了hash的步驟,實際的**中其實只要一棵4個葉子節點的樹即可)

(紅色的為個數)

我們建立的[1, l-1] (也就是[1, 4])之間的樹為

[1, r]也就是[1, 10]的樹為

兩樹相減得到

我們來找第5小的數:

發現左子樹為5  所以第5小的數在左邊, 再往下(左4右1) 發現左邊小於5了 ,所以第5小的數在右邊 所以第5小的數就是3了

同樣的,我們只要建立[1, i] (i是1到n之間的所有值)的所有樹,每當詢問[l, r]的時候,只要用[1, r]的樹減去[1, l-1]的樹,再找第k小就好啦

我們將這n個樹看成是建立在乙個大的線段樹里的,也就是這個線段樹的每個節點都是乙個線段樹( ——這就是主席樹)

最初所有的樹都是空樹,我們並不需要建立n個空樹,只要建立乙個空樹,也就是不必每個節點都建立乙個空樹

插入元素時,我們不去修改任何的結點,而是返回乙個新的樹( ——這就是函式式線段樹)

因為每個節點都不會被修改,所以可以不斷的重複用,因此插入操作的複雜度為o(logn)

總的複雜度為o((n+m)lognlogn)   (聽說 主席樹的芭比說 加上垃圾**, 可以減少乙個log~~~ 然而這只是聽說)

你以為這樣就結束了嗎!!

你沒有發現這樣空間大到**嗎!!!

你在每個節點都建了乙個線!段!樹!這不mle才有鬼呢!!!

那怎麼辦呢?ti

'>ti

ti表示一棵[1, i]區間的線段樹

那麼ti

'>ti

ti與ti−

1'>ti−1

ti−1的區別就只有當前插入的這個元素a

i'>ai

ai以及它的父親以及他父親的父親以及他父親的父親的父親...

也就是改變的就只有他和他上面logn個數

所以,我們並不需要建一整棵樹,我們只需要 單獨建立logn個結點,跟ti−

1'>ti−1

ti−1連起來就好了

這樣樹的空間複雜度(nlogn)

個人認為關鍵是理解主席樹保留了各個歷史版本的線段樹。

**如下:

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

typedef long long ll;

const int inf = 0x3f3f3f3f;

const int maxn =100000+10;

int n,m,cnt,root[maxn],a[maxn];

struct nodet[maxn*40];

vectorvec;

int getid(int x)

void update(int l,int r,int &x,int y,int pos)

int mid=(l+r)/2;

if(mid>=pos)else

}int query(int l,int r,int x,int y,int k)

int mid=(l+r)/2;

int sum=t[t[y].l].sum-t[t[x].l].sum;

//之所以是.l,見上面的例子

if(sum>=k)else

}int main()

sort(vec.begin(),vec.end());

vec.erase(unique(vec.begin(),vec.end()),vec.end());

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

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

return 0;

}

主席樹模板 POJ2104

離散化 對陣列排完序後用unique去重,unique返回的是去重後的陣列的末位址,減去第乙個元素的位址就能得到去重後的陣列大小,用lower bound查詢原數字在排序去重後的序列中的位序,用位序代替數字完成離散化。include include using namespace std defin...

主席樹模板(poj2104)

主席樹是可持久化線段樹,可以記錄線段樹的歷史版本。中和線段樹不同的是,l,r記錄的是左右子樹編號,因為普通的線段樹版本中,左右子樹自然就是o 1和o 1 1,但是主席樹中並不保證這個特性,所以需要記錄一下。是 include include include include include using...

Poj 2104 主席樹入門

題目 靜態查詢區間第 大 主席樹入門題目,之前看的很多資料一上來就是動態區間第 大,看得很費勁,後來找了個寫得清晰的,感覺靜態的還不算難,也不長 author cwind pragma comment linker,stack 102400000,102400000 include include ...