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 }

 

posted @ 2014-09-28 21:08  Naturain  阅读(135)  评论(0)    收藏  举报