Description
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 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
題目大意:給一串數字,多次詢問區間的第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 }
