題目大意:
求序列的區間第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 ...