POJ 2104 K-th Number(主席樹——附講解)


Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

 

題目大意:給一串數字,多次詢問區間的第k小值

思路:不帶修改的主席樹

 

附資料:http://blog.csdn.net/metalseed/article/details/8045038

http://www.abandonzhang.com/archives/29

——————————————————————————————————————————————————————————————————————————————————————————————

以下轉自http://prominences.weebly.com/1/post/2013/02/1.html

可持久化線段樹,也叫作函數式線段樹,也就是主席樹,(。。。因為先驅就是fotile主席。。Orz。。。)
網上的教程很少啊,有的教程寫得特別簡單,4行中文,然后就是一篇代碼~~
這里,我將從查找區間第k小值(不帶修改)題的可持久化線段樹做法中,講一講主席樹。
/*只是略懂,若有錯誤,還請多多包涵!*/
可持久化數據結構(Persistent data structure)就是利用函數式編程的思想使其支持詢問歷史版本、同時充分利用它們之間的共同數據來減少時間和空間消耗。/*找不到比較科學的定義,就拿這個湊湊數吧~~~*/
這個數據結構很坑啊,我研究了一整天才差不多理解了一些(。。太笨了。。。)。所以,要理解好每一個域或變量的意義。
開講!
一些數據結構,比如線段樹或平衡樹,他們一般是要么維護每個元素在原序列中的排列順序,要么是維護每個元素的大小順序,若是像二者兼得。。(反正我是覺得很。。)那么,這道題就想想主席樹吧~/*還可以用划分樹做*/
開講!~好像說過一邊了
既然叫函數式線段樹,那么就應該有跟普通線段樹相同的地方。一顆線段樹,只能維護一段區間里的元素。但是,每個詢問的區間都不一樣,若是對每段區間都單獨建立的線段樹,那~萎定了~。因此,就要想,如何在少建,或建得快的情況下,能利用一些方法,得出某個區間里的情況。
比如一棵線段樹,記為tree[i][j],表示區間[i,j]的線段樹。那么,要得到它的情況,可以利用另外兩棵樹,tree[1][i-1]tree[1][j],得出來。也就是說,可以由建樹的一系列歷史版本推出。
那么,怎么創建這些樹呢?
首先,離散化數據。因為如果數據太大的話,線段樹會爆~~
在所有樹中,是按照當前區間元素的離散值(也就是用大小排序)儲存的,在每個節點,存的是這個區間每個元素出現的次數之和(data域)。出現的次數,也就是存了多少數進來(建樹時,是一個數一個數地存進來的)。
先建議棵線段樹,所有的節點data域為0。再一個節點一個節點地添加。把每個數按照自己的離散值,放到樹中合適的位置,然后data+1,回溯的時候也要+1。當然,不能放到那棵空樹中,要重新建樹。第i棵樹存的是區間(原序列)[1,i]。但是,如果是這樣,那么會MLE+TLE。因此,要充分利用歷史版本。用兩個指針,分指當前空樹和前一棵樹。因為每棵樹的結構是一樣的,只是里面的data域不同,但是兩棵相鄰的樹,只有一數只差,因此,如果元素要進左子樹的話,右子樹就會跟上個樹這個區間的右子樹是完全一樣的,因此,可以直接將本樹本節點的右子樹指針接到上棵樹當前節點的右兒子,這樣即省時間,又省空間。
每添加一個節點(也就是新建一棵樹)的復雜度是O(logn),因此,這一步的復雜度是O(nlogn)
建完之后,要怎么查找呢?
跟一般的,在整棵樹中找第k個數是一樣的。如果一個節點的左權值(左子樹上點的數量之和)大於k,那么就到左子樹查找,否則到右子樹查找。其實主席樹是一樣的。對於任意兩棵樹(分別存區間[1,i]和區間[1,j] i<j),在同一節點上(兩節點所表示的區間相同),data域之差表示的是,原序列區間[i,j]在當前節點所表示的區間里,出現多少次(有多少數的大小是在這個區間里的)。同理,對於同一節點,如果在兩棵樹中,它們的左權值之差大於等於k,那么要求的數就在左孩子,否則在右孩子。當定位到葉子節點時,就可以輸出了。

——————————————————————————————————————————————————————————————————————————————————————————————

鄙人的一些理解:所謂主席樹呢,就是對原來的數列[1..n]的每一個前綴[1..i](1≤i≤n)建立一棵線段樹,線段樹的每一個節點存某個前綴[1..i]中屬於區間[L..R]的數一共有多少個(比如根節點是[1..n],一共i個數,sum[root] = i;根節點的左兒子是[1..(L+R)/2],若不大於(L+R)/2的數有x個,那么sum[root.left] = x)。若要查找[i..j]中第k大數時,設某結點x,那么x.sum[j] - x.sum[i - 1]就是[i..j]中在結點x內的數字總數。而對每一個前綴都建一棵樹,會MLE,觀察到每個[1..i]和[1..i-1]只有一條路是不一樣的,那么其他的結點只要用回前一棵樹的結點即可,時空復雜度為O(nlogn)。

 

代碼(最原始的樹所有結點的值都為0,就算建好一棵樹了……):

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int MAXN = 100010;
 6 
 7 struct Node {
 8     int L, R, sum;
 9 };
10 Node T[MAXN * 20];
11 int T_cnt;
12 
13 void insert(int &num, int &x, int L, int R) {
14     T[T_cnt++] = T[x]; x = T_cnt - 1;
15     ++T[x].sum;
16     if(L == R) return ;
17     int mid = (L + R) >> 1;
18     if(num <= mid) insert(num, T[x].L, L, mid);
19     else insert(num, T[x].R, mid + 1, R);
20 }
21 
22 int query(int i, int j, int k, int L, int R) {
23     if(L == R) return L;
24     int t = T[T[j].L].sum - T[T[i].L].sum;
25     int mid = (R + L) >> 1;
26     if(k <= t) return query(T[i].L, T[j].L, k, L, mid);
27     else return query(T[i].R, T[j].R, k - t, mid + 1, R);
28 }
29 
30 struct A {
31     int x, idx;
32     bool operator < (const A &rhs) const {
33         return x < rhs.x;
34     }
35 };
36 
37 A a[MAXN];
38 int rank[MAXN], root[MAXN];
39 int n, m;
40 
41 int main() {
42     T[0].L = T[0].R = T[0].sum = 0;
43     root[0] = 0;
44     while(scanf("%d%d", &n, &m) != EOF) {
45         for(int i = 1; i <= n; ++i) {
46             scanf("%d", &a[i].x);
47             a[i].idx = i;
48         }
49         sort(a + 1, a + n + 1);
50         for(int i = 1; i <= n; ++i) rank[a[i].idx] = i;
51         T_cnt = 1;
52         for(int i = 1; i <= n; ++i) {
53             root[i] = root[i - 1];
54             insert(rank[i], root[i], 1, n);
55         }
56         while(m--) {
57             int i, j, k;
58             scanf("%d%d%d", &i, &j, &k);
59             printf("%d\n", a[query(root[i - 1], root[j], k, 1, n)].x);
60         }
61     }
62 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM