POJ 2104 K-th Number(主席树——附讲解)
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 109 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 }