Book--划分树
2014-09-28 20:38:10
POJ的线段树刷了好几道,突然碰到一道求区间第K大的问题,搁置了两天,等不及要学一下了。
首先,这题的解法多样,(1)经典的划分树 (2)线段树维护归并树 (3)主席树 (orz)
看了几篇博客&文献,比较好的:(1)百度文库 (2)博客,引用一下。。
First. 划分树的定义
划分树定义为,它的每一个节点保存区间[lft,rht]所有元素,元素顺序与原数组(输入)相同,但是,两个子树的元素为该节点所有元素排序后(rht-lft+1)/2个进入左子树,其余的到右子树,同时维护一个num域,num[i]表示lft->i这个点有多少个进入了左子树。
Second.划分树的Sample

如果由下而上看这个图,我们就会发现它和归并排序的(归并树)的过程很类似,或者说正好相反。归并树是由下而上的排序,而它确实是由上而下的排序(观察’4’的运动轨迹,我们可以猜到,划分树的排序也是一种稳定的排序方法,这里不是说明的重点,不予证明),但这正是它可以用来解决第k大元素的理由所在。(具体的理由,写完再补)
以POJ 2104 这道裸的经典划分树问题作为副本:
1 /************************************************************************* 2 > File Name: 2104.cpp 3 > Author: Nature 4 > Mail: 564374850@qq.com 5 > Created Time: Sun 28 Sep 2014 07:24:07 PM CST 6 ************************************************************************/ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <cstdlib> 11 #include <iostream> 12 #include <algorithm> 13 using namespace std; 14 #define getmid(l,r) (l + (r - l) / 2) 15 const int RA = 100010; 16 17 int sorted[RA]; 18 19 struct node{ 20 int val[RA]; //记录某一层所有数的值 21 int num[RA]; //num[k]:某一层第k个数及其之前有多少个数划入左子树 22 }t[20]; //t只用开到足够层数即可,log(n)级别 23 24 void Build_ptree(int p,int l,int r){ 25 if(l == r) 26 return; 27 int mid = getmid(l,r); 28 int isame = mid - l + 1; //isame:该层中与中值相等,且被划入左子树的元素个数 29 int same = 0; //记录当前与中值相等的元素个数 30 for(int i = l; i <= r; ++i){ 31 if(t[p].val[i] < sorted[mid]) --isame; //处理后isame意义成立 32 } 33 int ls = l,rs = mid + 1; //ls:左子树元素起始位置,rs:右子树元素起始位置 34 for(int i = l; i <= r; ++i){ 35 t[p].num[i] = (i == l ? 0 : t[p].num[i - 1]); 36 if(t[p].val[i] < sorted[mid]){ //比中值小的元素归入左子树 37 t[p].num[i]++; 38 t[p + 1].val[ls++] = t[p].val[i]; 39 } 40 else if(t[p].val[i] > sorted[mid]){ //比中值大的元素归入右子树 41 t[p + 1].val[rs++] = t[p].val[i]; 42 } 43 else{ //与中值相等,考虑same是否已经达到isame 44 if(same < isame){ //未达到,归入左子树 45 same++; 46 t[p].num[i]++; 47 t[p + 1].val[ls++] = t[p].val[i]; 48 } 49 else{ //已达到,归入右子树 50 t[p + 1].val[rs++] = t[p].val[i]; 51 } 52 } 53 } 54 Build_ptree(p + 1,l,mid); 55 Build_ptree(p + 1,mid + 1,r); 56 } 57 58 int Query_ptree(int a,int b,int k,int p,int l,int r){ 59 if(l == r) 60 return t[p].val[a]; 61 //ln(rn):[a,b]中归入左(右)子树的元素个数 62 //preln(prern):[l,a - 1]中归入左(右)子树的元素个数 63 int ln,rn,preln,prern,sum; 64 int mid = getmid(l,r); 65 ln = t[p].num[b] - (a == l ? 0 : t[p].num[a - 1]); 66 preln = (a == l ? 0 : t[p].num[a - 1]); 67 if(ln >= k){ //[a,b]中划入左子树的元素 >= k,则递归进左子树 68 a = l + preln; 69 b = l + preln + ln - 1; 70 return Query_ptree(a,b,k,p + 1,l,mid); 71 } 72 else{ //否则,递归进右子树,找第 k - ln 大的元素 73 prern = a - l - preln; 74 rn = b - a + 1 - ln; 75 a = mid + 1 + prern; 76 b = mid + 1 + prern + rn - 1; 77 return Query_ptree(a,b,k - ln,p + 1,mid + 1,r); 78 } 79 } 80 81 int main(){ 82 int n,m,a,b,k; 83 scanf("%d%d",&n,&m); 84 for(int i = 1; i <= n; ++i){ 85 scanf("%d",&sorted[i]); 86 t[1].val[i] = sorted[i]; //注意在输入时要给划分树第一层初始化 87 } 88 sort(sorted + 1,sorted + n + 1); 89 Build_ptree(1,1,n); 90 while(m--){ 91 scanf("%d%d%d",&a,&b,&k); 92 printf("%d\n",Query_ptree(a,b,k,1,1,n)); 93 } 94 return 0; 95 }

浙公网安备 33010602011771号