考慮這樣乙個問題:
給出一段長度為n序列
,對於一些詢問
請輸出序列上[l
,r] 內第k大的數。
關於暴力做法,其實是很簡單的,但是會超時,在此略過。
有一種辦法,是利用字首和的思想。先將
離散到區間[1
,n] ,然後,對於任意節點
i ,都建立一棵權值線段樹,代表離散後
在權值區間[1
,n] 出現的次數。
這樣,對於序列上的某一段[l
,r] ,我們就可以通過權值線段樹r和l-1某個節點儲存的權值出現次數相減,得到序列上這一段在這個節點代表的權值區間上的數字個數。(tips:請注意區分序列上區間和權值線段樹節點代表的區間)
如果不考慮記憶體,那麼問題已經解決了,但是對於序列上的任意一點,我們都建立了一棵線段樹,此時空間複雜度已經達到平方級別,顯然不行。
但是考慮到乙個巧妙的現象,對於第
i 棵線段樹,其實比起前一棵來說就多了乙個數字,但是我們卻新建了一整棵線段樹!是不是有一點不划算啊2333
首先明確乙個顯而易見的事實:無論如何,這n棵權值線段樹的結構形態都是一樣的。
對於權值線段樹上的任意乙個節點,新加入乙個屬於它的值進來,顯然這個節點的值要+1,然後再向下更新。但是,它就只有兩個兒子節點,這個更新的方向不是往左就是往右。
那麼這時對於第
i棵線段樹,它的每一層,其實都只有乙個節點和第i−
1 棵的相應節點不一樣。按道理說,這個線段樹真正需要記錄的就只是新建一條從上到下的鏈(需要記錄子節點是左還是右)來記錄,而不是新建一整棵線段樹。
如何確定不用更改的那些呢?很簡單,對於這條鏈上的任意乙個非葉子節點,它有兒子的那一邊就不管,另乙個子節點直接沿用上一棵線段樹的相應節點就行了。最後向上更新就行了。
#include
using
namespace
std ;
bool read ( int &x ) while ( isdigit(c) ) if (f) x = -x ;return
true ; }
void print ( int x ) if ( x < 0 ) while (x) while (len) putchar(a[len--]+'0') ;}
const
int maxn = 100010 ;
int n, m, rt[maxn], rank[maxn], cnt ;
struct node tree[maxn<<5] ;
struct nodd
} a[maxn] ;
void insert ( int k, int& x, int l, int r )
int query ( int rt1, int rt2, int l, int r, int k )
int main()
sort ( a+1, a+n+1 ) ;
for ( i = 1 ; i <= n ; i ++ )
a[i].id[rank] = i ;
for ( i = 1 ; i <= n ; i ++ )
while ( m-- )
return
0 ;}
是不是挺簡單的啊~ 可持久化線段樹 主席樹(靜態)
參考部落格 先介紹一下主席樹,主席樹也稱函式式線段樹也稱可持久化線段樹。其實就是支援查詢歷史版本,這個在看完之後就會了解 其實主席樹就是很多線段樹組合的總體,從它的其它稱呼也可以看出來了,其實它本質上還是線段樹。主席樹就是利用函式式程式設計的思想來使線段樹支援詢問歷史版本 同時充分利用它們之間的共同...
可持久化線段樹總結(可持久化線段樹,線段樹)
最近正在學習一種資料結構 可持久化線段樹。看了網上的許多部落格,弄了幾道模板題,思路有點亂了,所以還是來總結整理下吧。你需要維護這樣的乙個長度為 n 的陣列,支援如下幾種操作 在某個歷史版本上修改某乙個位置上的值 訪問某個歷史版本上的某一位置的值 此外,每進行一次操作 對於操作2,即為生成乙個完全一...
可持久化線段樹
可持久化線段樹,意思是可以查詢歷史記錄的線段樹。又叫主席樹。我們可以通過記錄不同的根節點,並在每乙個更新到的節點處新建必要的節點。詢問不同版本的主席樹,只需要進入不同的根節點即可。例題 給定n,m,輸入n個數組成的數列,有m個詢問,每次詢問l,r這個區間中,第k小的數的值。分析 這個題可以巧妙運用主...