快樂找到區間第K大 劃分樹

2021-09-25 14:35:39 字數 3522 閱讀 3851

劃分樹,顧名思義就是將乙個序列,劃分成很多小部分。其作用就是可以快樂地找到給定區間第k大的數。其實使用歸併樹和快排也都可以找到區間內第k大的數,但其效率都不如劃分樹要好,快排過的時間複雜度o(n x m),而劃分樹是o(n x logn)。

劃分樹原理:1.建樹:根結點就是原序列,左孩子儲存父結點所有元素排序後的一半,右孩子也存一半,也就是說排名1 -> mid的存在左邊,排名(mid+1) -> r 的存在右邊,同一結點上每個元素保持原序列中相對的順序,藍色數字表示其是在左子樹中的,最後通過遞迴左右子樹建立整棵樹,同時在建樹的過程中記錄乙個輔助陣列num[i],其意義是從 l 到 i 其中小於中間值的元素個數。

(來自hchlqlz部落格)

圖示:

2.查詢:對於在區間[l,r]中的第k大的詢問,先比較num[r]- num[l-1](在區間[l,r]中在左子樹中的元素個數) 和k的值。

如果num[r]- num[l-1] >= k(左子樹中的個數大於k[l,r]中的第k大的數字在左子樹,到左子樹中去找。

否則第k大的元素就在右子樹。此處要注意遞迴到左右子樹時,lrk值的下一狀態。

舉個栗子:

比如要查詢2 到6 之間第3 大的數,那麼先判斷2 到6 之間有多少元素進入左子樹,(在此忽略細節)num[6]-num[2-1]=2,就說明2 到6 有兩個數進入左子樹,又因為我們要找的是第3 大的數,所以一定在右子樹中

劃分樹步驟:

1.建樹

可以發現,劃分樹的每一層結點都是n個,所以採用乙個tree[max][max]的二維陣列來儲存整棵樹,同樣num陣列也採用同樣的方式儲存。

a.將原陣列sort後,存在乙個陣列sorted中,在每個區間中找到區間中間值mid(偏左)

b.掃瞄原陣列在區間[l,r]的部分,將l到r中的小於等於mid的陣列個數記錄下來在num陣列中

c.將小於等於mid的樹放到左子樹,將大於mid的放到右子樹

d.向左、右子樹開始遞迴建樹  遞迴基:r == l 時,返回

2.查詢區間[l,r]中第k大的元素

if(k > num[r]-num[l-1]) 進入右子樹

if(k <= num[r]-num[l-1]) 進入左子樹

下面以poj 2014為例 給出**

題意:給出n個數,進行m詢問,每次詢問給出l,r,k 找出區間[l,r]的第k大的數。

**:

/*

1.建樹

a.將原陣列sort後,存在乙個陣列sorted中,在每個區間中找到區間中間值mid

b.掃瞄原陣列在區間[l,r]的部分,將l到r中的小於等於mid的陣列個數記錄下來在num陣列中

c.將小於等於mid的樹放到左子樹,將大於mid的放到右子樹

d.向左、右子樹開始遞迴建樹

遞迴基:r == l 時,返回

2.查詢區間[l,r]中第k大的元素

判斷num[r]-num[l-1]與k值的大小

a.if(k > num[r]-num[l-1]) 進入左子樹

if(k <= num[r]-num[l-1]) 進入右子樹

tip:1.注意查詢遞迴轉移的s,e,l,r的引數更新變化

2.注意每次區間中num陣列的要初始化

*/#include

#include

#include

#include

using

namespace std;

const

int max =

100010

;int n,m,mid_sort,last,ans=0;

int a[max]

,sorted[max]

;int tree[20]

[max]

,num[20]

[max]=;

void

creat

(int l,

int r,

int layer)

//找到中間點的位置

int mid =

(l+r)

>>1;

mid_sort = sorted[mid]

;//左右子樹的起始位置

int cnt_l = l-

1,cnt_r = mid;

//cnt_same:當前結點可以容納多少個和mid_sort一樣的元素的上限

//cnt_same值為l到mid的元素個數減去l到r中小於mid_sort的元素個數

int cnt_same = mid-l+1;

for(

int i = l; i <= r; i++)if

(tree[layer]

[i]< mid_sort) cnt_same--

;for

(int i = l; i <= r; i++

)else

}creat

(l,mid,layer+1)

;creat

(mid+

1,r,layer+1)

;}//s代表當前區間的左界,e代表當前區間的右界,layer為樹的層數 ,l是查詢的左界,r是查詢右界,k是題中定義

intquery

(int s,

int e,

int layer,

int l,

int r,

int k)

//ll表示 l 前面有多少元素進入左孩子

int ll ;

if(l != s) ll= num[layer]

[l-1];

else ll =0;

//toleft表示左孩子裡有多少元素

int toleft;

int mid =

(s+e)

>>1;

toleft = num[layer]

[r]- ll;

//cout<

(toleft >= k)

//s+ll是掠過s之前(不包含s)的被分到左孩子裡的元素,s+num[layer][r]-1也是相似意思

return

query

(s,mid,layer+

1,s+ll,s+num[layer]

[r]-

1,k)

;else

}int

main()

return0;

}

參考內容:

靜態區間第k大(劃分樹)

poj 2104為例 經典劃分樹問題 思想 利用快速排序思想,include include using namespace std const int maxn 100010 int tree 20 maxn 每層每個位置的值 int sorted maxn 排好序的陣列,方便尋找中值 int l...

區間第k大(主席樹)

學了一下主席樹模板題,當初看了網上的主席樹講解都沒有看懂,後面看了嗶哩嗶哩的uestc的主席樹,終於看懂了思想。每次更新的複雜度都為logn。每次更新的話就是對要更新的點路徑上的點重新更加乙個,然後進行對沒有影響的那些進行連邊。然後用乙個root記錄每乙個線段樹的根節點下標。include incl...

主席樹區間第K大

主席樹的實質其實還是一顆線段樹,然後每一次修改都通過上一次的線段樹,來新增新邊,使得每次改變就改變logn個節點,很多節點重複利用,達到節省空間的目的。1.不帶修改的區間第k大。hdu 2665 模板題 1 include2 using namespace std 3 define fopen fr...