1. 程式人生 > >可持久化線段樹——Step 1 靜態區間第K大

可持久化線段樹——Step 1 靜態區間第K大

考慮這樣一個問題:
給出一段長度為n序列{ai},對於一些詢問{L,R,K}請輸出序列上[L,R]內第K大的數。

關於暴力做法,其實是很簡單的,但是會超時,在此略過。

有一種辦法,是利用字首和的思想。先將{ai}離散到區間[1,n],然後,對於任意節點i,都建立一棵權值線段樹,代表離散後{a1,ai}在權值區間[1,n]出現的次數。

這樣,對於序列上的某一段[L,R],我們就可以通過權值線段樹R和L-1某個節點儲存的權值出現次數相減,得到序列上這一段在這個節點代表的權值區間上的數字個數。(Tips:請注意區分序列上區間和權值線段樹節點代表的區間)

如果不考慮記憶體,那麼問題已經解決了,但是對於序列上的任意一點,我們都建立了一棵線段樹,此時空間複雜度已經達到平方級別,顯然不行。

但是考慮到一個巧妙的現象,對於第i棵線段樹,其實比起前一棵來說就多了一個數字,但是我們卻新建了一整棵線段樹!是不是有一點不划算啊2333

首先明確一個顯而易見的事實:無論如何,這n棵權值線段樹的結構形態都是一樣的。
對於權值線段樹上的任意一個節點,新加入一個屬於它的值進來,顯然這個節點的值要+1,然後再向下更新。但是,它就只有兩個兒子節點,這個更新的方向不是往左就是往右。

那麼這時對於第i棵線段樹,它的每一層,其實都只有一個節點和第i1棵的相應節點不一樣。按道理說,這個線段樹真正需要記錄的就只是新建一條從上到下的鏈(需要記錄子節點是左還是右)來記錄,而不是新建一整棵線段樹。
如何確定不用更改的那些呢?很簡單,對於這條鏈上的任意一個非葉子節點,它有兒子的那一邊就不管,另一個子節點直接沿用上一棵線段樹的相應節點就行了。最後向上更新就行了。

給出參考程式碼:

#include <bits/stdc++.h>
using namespace std ;
bool Read ( int &x ) { char c = getchar() ; x = 0 ; bool f = 0 ; while ( !isdigit(c) ) { if ( c == '-' ) f = 1 ; if ( c == EOF ) return false ; c = getchar() ; } while ( isdigit(c) ) { x = 10 * x + c - '0' ; c = getchar() ; } if (f) x = -x ;return
true ; } void Print ( int x ) { int len = 0, a[50] ; if ( x == 0 ) { putchar('0') ; return ; } if ( x < 0 ) { putchar('-') ; x = -x ; } while (x) { a[++len] = x%10 ; x /= 10 ; } while (len) putchar(a[len--]+'0') ;} const int maxn = 100010 ; int n, m, rt[maxn], Rank[maxn], cnt ; struct node { int l, r, v ; } tree[maxn<<5] ; struct nodd { int v, id ; friend bool operator < ( nodd a, nodd b ) { return a.v == b.v ? a.id < b.id : a.v < b.v ; } } a[maxn] ; void insert ( int K, int& x, int l, int r ) { tree[++cnt] = tree[x] ; x = cnt ; ++ tree[x].v ; int mid = l+r >> 1 ; if ( l == r ) return ; if ( K <= mid ) insert ( K, x[tree].l, l, mid ) ; else insert ( K, x[tree].r, mid+1, r ) ; } int query ( int rt1, int rt2, int l, int r, int K ) { if ( l == r ) return l ; int mid = l+r >> 1 ; int num = tree[rt2].l[tree].v - tree[rt1].l[tree].v ; if ( K <= num ) return query ( rt1[tree].l, rt2[tree].l, l, mid, K ) ; else return query ( rt1[tree].r, rt2[tree].r, mid+1, r, K-num ) ; } int main() { int i, j, k, x, y ; Read(n) ; Read(m) ; for ( i = 1 ; i <= n ; i ++ ) { Read(a[i].v) ; a[i].id = i ; } sort ( a+1, a+n+1 ) ; for ( i = 1 ; i <= n ; i ++ ) a[i].id[Rank] = i ; for ( i = 1 ; i <= n ; i ++ ) { rt[i] = rt[i-1] ; insert ( Rank[i], rt[i], 1, n ) ; } while ( m-- ) { Read(x) ; Read(y) ; Read(k) ; Print( a[query( rt[x-1], rt[y], 1, n, k )].v ) ; putchar('\n') ; } return 0 ; }

是不是挺簡單的啊~

相關推薦

持久化線段——Step 1 靜態區間K

考慮這樣一個問題: 給出一段長度為n序列{ai},對於一些詢問{L,R,K}請輸出序列上[L,R]內第K大的數。 關於暴力做法,其實是很簡單的,但是會超時,在此略過。 有一種辦法,是利用字首和的思想。先將{ai}離散到區間[1,n],然後,對於任意節點i,

HDU2665 主席原理解決靜態區間K值問題總結 有詳細圖解和程式碼解釋

鄙人不才,剛學習了一點主席樹,想自己來寫一篇關於主席樹的詳解,主要針對主席樹解決靜態(無修改)區間內第K大值的問題,可以參考HDU 2665。解決其他的問題的主席樹演算法等自己搞懂後再補上。下文如果有什麼錯誤還請指出,感激不盡! 感謝以下博文對主席樹的講解: 1.主席樹1

HDU 4348.To the moon-持久化線段(帶修改線上區間更新(增減)、區間求和,延時標記不下放(空間優化))

  To the moon Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 8372 

[poj 2104]主席+靜態區間k

include end 區間 得到 name int 題目 tar tdi 題目鏈接:http://poj.org/problem?id=2104 主席樹入門題目,主席樹其實就是可持久化權值線段樹,rt[i]維護了前i個數中第i大(小)的數出現次數的信息,通過查詢兩棵樹的差

靜態區間k 解法

i++ val pri math 1+n std -a 平衡樹 scanf 然而過不去你谷的模板 思路: 值域線段樹\([l,r]\)代表一棵值域在\([l,r]\)範圍內的點構成的一顆平衡樹 平衡樹的\(BST\)權值為點在序列中的位置 查詢區間第\(k\)大值時 左區間

靜態區間k(歸併

POJ 2104為例 思想: 利用歸併排序的思想: 建樹過程和歸併排序類似,每個數列都是子樹序列的合併與排序。 查詢過程,如果所查詢區間完全包含在當前區間中,則直接返回當前區間內小於所求數的

HDU 2665 Kth number(主席靜態區間K)題解

可持久化 unique algorithm using 主席樹 可持久化線段樹 long spa 靜態區 題意:問你區間第k大是誰 思路:主席樹就是可持久化線段樹,他是由多個歷史版本的權值線段樹(不是普通線段樹)組成的。 具體可以看q學姐的B站視頻 代碼:

Permutation UVA - 11525(值域狀數組,狀數組區間k(離線),log方,log)

一次 跳過 += 數字 div ret num while printf Permutation UVA - 11525 看康托展開 題目給出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展開(將n個數的所有排列按字典序

ZOJ -2112 Dynamic Rankings 主席 待修改的區間K

OS alt \n tar txt push fopen amp get Dynamic Rankings 帶修改的區間第K大其實就是先和靜態區間第K大的操作一樣。先建立一顆主席樹, 然後再在樹狀數組的每一個節點開線段樹(其實也是主席樹,共用節點), 每次修改的時候都按照

整體二分初步——靜態區間k

Description 給定一個長度為n的序列,m個詢問,每個詢問的形式為:L,r,k表示在[L,r]間中的第k大元素。 Input 第1行:2個數,n,m表示序列的長度和詢問的個數 第2行:n個數,表示n個數的大小 第3-m+2行:每行3個數,L,r,k表示詢問在[L

靜態區間K

<a target=_blank href="http://poj.org/problem?id=2104" target="_blank"><span style="font-s

2665 Kth number (靜態區間k

Give you a sequence and ask you the kth big number of a inteval. InputThe first line is the numbe

POJ 題目2985 The k-th Largest Group(線段單點更新求k值,並查集)

The k-th Largest Group Time Limit: 2000MS Memory Limit: 131072K Total Submissions: 7869 Accepted: 2534 Description Newman likes play

落谷 P3834 持久化線段 1(主席)(區間k小)

設區間為l,r,用r版本減去l版本求出區間第k小,一個板子 #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using

【模板】持久化線段 1(主席

base math 一次 數據 mar 指定 das min 第k小 題目背景 這是個非常經典的主席樹入門題——靜態區間第K小 數據已經過加強,請使用主席樹。同時請註意常數優化 題目描述 如題,給定N個正整數構成的序列,將對於指定的閉區間

[Luogu] 持久化線段 1(主席

tdi ace oid root post space out 節點 nod https://www.luogu.org/problemnew/show/P3834 #include<cstdio> #include<iostream> #

【XSY2720】區間k小 整體二分 持久化線段

cpp markdown 區間 序列 printf line 線段 using back 題目描述   給你你個序列,每次求區間第\(k\)小的數。   本題中,如果一個數在詢問區間中出現了超過\(w\)次,那麽就把這個數視為\(n\)。   強制在線。   \(n\leq

【9018:2207】持久化線段1

正整數 page sta 輸出 amp status efi oid 所有 2207: 【模板】可持久化線段樹1 時間限制: 2 Sec 內存限制: 256 MB提交: 45 解決: 14[提交][狀態][討論版] 題目描述 你需要維護1個數列的若幹版本: 對於給定的

【刷題】洛谷 P3834 【模板】持久化線段 1(主席

!= tchar 這樣的 信息 reg har mem hair define 題目背景 這是個非常經典的主席樹入門題——靜態區間第K小 數據已經過加強,請使用主席樹。同時請註意常數優化 題目描述 如題,給定N個正整數構成的序列,將對於指定的閉區間查詢其區間內的第K小值。

持久化線段 區間k

getchar() AI 多少 lin urn pri 長度 != register 2018-04-04 http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1175 一個長度為N的整數序列,編號0