划分树详解 结合例题hdu4251

来源:互联网 发布:sql 某个字段包含字符 编辑:程序博客网 时间:2024/05/16 15:28

参考博客:

    http://www.notonlysuccess.com/index.php/divide-tree/#more-142

    http://blog.csdn.net/fp_hzq/article/details/7993364

    http://www.cnblogs.com/pony1993/archive/2012/07/17/2594544.html

本文出自 “每天进步一点点” 博客,请务必保留此出处http://sbp810050504.blog.51cto.com/2799422/1008930



用划分树来解决选定区间内的第K大值,其实也就两步!一步是建树,另一步则是查询。

     先说我对建树的理解吧!

    建树的过程很简单:两步就OK了!

   第一步:找到序列的中位数,把大于中位数的扔到中位数的左边,小于中位数的扔到数的右边。这样整个序列就被分成了两个区间。

   第二步:对每个子区间,也分别执行第一步操作,直到序列中只有一个元素为止。

   可以看出,建树是一个递归的过程,与线段树的建树有相似之处。

   划分树的建树需要注意以下几点:

     第一:建树是分层的,所以代码中用的是二维数组tree[20][M]一般10W级别的数据,20层已经够了。

     第二:建树划分的标准是中位数,所以需要排序。而且只排一次序就OK了,为什么只排一次就OK了,我很久都没明白这一点。其实是这样的:对于任意序列: 划分后,左边的数据永远不会大于右边的数据。那么对左边数据单独排序与整体排序的结果是一样的,所以排一次序就OK了!

     第三:划分树划分好的数据永远在存放在下一层。比如数据:

tree[0][M]=1 5 2 6 3 7 4

排序后为:1 2 3 4 5 6 7

中位数为:4

划分后的结果为:tree[1][M]=1 2 3 4 5 6 7(这组数据有点特殊,划分后来就已经是排好序的了)红黑色字体都仍按原未排顺序排列

(红色表示划分到中位数的左边,黑色表示划分到中位数的右边)

接着划分:tree[2][M]=1 2 3 4 5 6 7

再接着分:tree[3][M]=2 4 6 0

到这里已经分完了,为什么最后是0呢?在第2层(tree[2][M],7已经分完了,所以不用再分

 第四:划分到最后,实际上已经对序列进行排序了。

     划分的时候还有一点需要处理:如果有多个数据相同怎么办呢?通过一种特殊的处理:尽量使左右两边平均分配相同的数。这个特殊处理是这样的:

    在没分之前,先假设中位数左边的数据suppose都已经分到左边了,所以suppose=mid-left+1;然后如果真的分在左边,即if(tree[level][i]<sorted[mid])

suppose--suppose就减一!到最后,如果suppos=1,则说明中位数左边的数都小于中位数,如果有等于中位数的,则suppose大于1。 

    最后分配的时候,把suppose个数,分到左边就可以了,剩下的分到右边!因为suppose的初值是mid-left+1,这样就能保证中位数左边和右边的数平衡了!

   第五:划分的过程,需要把每层的数据记录:toLeft[20][M]toLeft[i][j]定义为:第i[1,j]之间有多

模板:
#include<stdio.h> #include<algorithm> using namespace std; #define M 100005 int tree[20][M],sorted[M]; int toLeft[20][M]; void build(int level,int left,int right){ if(left==right)return ; int mid=(left+right)>>1; int i; int suppose;//假设在中位数sorted[mid]左边的数都全部小于sorted[mid] suppose=mid-left+1; for(i=left;i<=right;i++){ if(tree[level][i]<sorted[mid]){ suppose--; } } //如果suppose==1,则说明数组中值为sorted[mid]只有一个数。比如序列:1 3 4 5 6,sorted[mid]=4 /*如果suppose>1,则说明数组中左半边值为sorted[mid]的不止一个数,为mid-suppose。比如序列:1 4 4 4 6,sorted[mid]=4 */ int lpos=left,rpos=mid+1; for(i=left;i<=right;i++){ if(i==left){//这里是预处理,相当与初始化 toLeft[level][i]=0; }else{ toLeft[level][i]=toLeft[level][i-1]; } if(tree[level][i]<sorted[mid]){//划分到中位数左边 toLeft[level][i]++; tree[level+1][lpos++]=tree[level][i]; }else if(tree[level][i]>sorted[mid]){//划分到中位数右边 tree[level+1][rpos++]=tree[level][i]; }else{//这里,suppose大于0的数划分到中位数的左边 if(suppose!=0){//这里的处理太巧妙了!帅气! suppose--; toLeft[level][i]++; tree[level+1][lpos++]=tree[level][i]; }else{//表示 tree[level+1][rpos++]=tree[level][i]; } } } build(level+1,left,mid); build(level+1,mid+1,right); } //在[left,right]数据中查询[qleft,qright]中第k大的数据 int query(int level,int left,int right,int qleft,int qright,int k){ if( qleft==qright) return tree[level][qleft]; int s;//代表[left,qleft)之间有多个个元素被分到左边 int ss;//[qleft, qright]内将被划分到左子树的元素数目 int mid=(left+right)>>1; if(left==qleft){ s=0; ss=toLeft[level][qright]; }else{ s=toLeft[level][qleft-1]; ss=toLeft[level][qright]-s; } int newl,newr; if(k<=ss){//查询左边 newl=left+s; newr=left+s+ss-1; return query(level+1,left,mid,newl,newr,k); }else{//查询右边 newl=mid-left+1+qleft-s; newr=mid-left+1+qright-s-ss; return query(level+1,mid+1,right,newl, newr,k - ss); } } int main(){ int n,m; while(scanf("%d %d",&n,&m)!=EOF){         int i;         for(i=1;i<=n;i++){             scanf("%d",&tree[0][i]);             sorted[i]=tree[0][i];         }         sort(sorted+1,sorted+n+1);         build(0,1,n);         for(i=0;i<n;i++){             for(int j=1;j<=n;j++){                 printf("%d ",toLeft[i][j]);             }             printf("\n");         }         int ql,qr,k;         for(i=0;i<m;i++){             scanf("%d %d %d",&ql,&qr,&k);             printf("%d\n",query(0,1,n,ql,qr,k));         } } return 0; } 个数据被分到了左边(注意这里用的是闭区间)。

The Famous ICPC Team Again

Time Limit: 30000/15000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 558    Accepted Submission(s): 260


Problem Description
When Mr. B, Mr. G and Mr. M were preparing for the 2012 ACM-ICPC World Final Contest, Mr. B had collected a large set of contest problems for their daily training. When they decided to take training, Mr. B would choose one of them from the problem set. All the problems in the problem set had been sorted by their time of publish. Each time Prof. S, their coach, would tell them to choose one problem published within a particular time interval. That is to say, if problems had been sorted in a line, each time they would choose one of them from a specified segment of the line.

Moreover, when collecting the problems, Mr. B had also known an estimation of each problem’s difficultness. When he was asked to choose a problem, if he chose the easiest one, Mr. G would complain that “Hey, what a trivial problem!”; if he chose the hardest one, Mr. M would grumble that it took too much time to finish it. To address this dilemma, Mr. B decided to take the one with the medium difficulty. Therefore, he needed a way to know the median number in the given interval of the sequence.
 

Input
For each test case, the first line contains a single integer n (1 <= n <= 100,000) indicating the total number of problems. The second line contains n integers xi (0 <= xi <= 1,000,000,000), separated by single space, denoting the difficultness of each problem, already sorted by publish time. The next line contains a single integer m (1 <= m <= 100,000), specifying number of queries. Then m lines follow, each line contains a pair of integers, A and B (1 <= A <= B <= n), denoting that Mr. B needed to choose a problem between positions A and B (inclusively, positions are counted from 1). It is guaranteed that the number of items between A and B is odd.
 

Output
For each query, output a single line containing an integer that denotes the difficultness of the problem that Mr. B should choose.
 

Sample Input
55 3 2 4 131 32 43 5510 6 4 8 231 32 43 5
 

Sample Output
Case 1:332Case 2:664


题意:  输入n  以及n个数  然后输入m个区间 问每个区间中的中间大的数是哪个
#include<stdio.h> #include<algorithm> using namespace std; #define M 100005 int tree[20][M],sorted[M]; int toLeft[20][M]; void build(int level,int left,int right){ if(left==right)return ; int mid=(left+right)>>1; int i; int suppose;//假设在中位数sorted[mid]左边的数都全部小于sorted[mid] suppose=mid-left+1; for(i=left;i<=right;i++){ if(tree[level][i]<sorted[mid]){ suppose--; } } //如果suppose==1,则说明数组中值为sorted[mid]只有一个数。比如序列:1 3 4 5 6,sorted[mid]=4 /*如果suppose>1,则说明数组中左半边值为sorted[mid]的不止一个数,为mid-suppose。比如序列:1 4 4 4 6,sorted[mid]=4 */ int lpos=left,rpos=mid+1; for(i=left;i<=right;i++){ if(i==left){//这里是预处理,相当与初始化 toLeft[level][i]=0; }else{ toLeft[level][i]=toLeft[level][i-1]; } if(tree[level][i]<sorted[mid]){//划分到中位数左边 toLeft[level][i]++; tree[level+1][lpos++]=tree[level][i]; }else if(tree[level][i]>sorted[mid]){//划分到中位数右边 tree[level+1][rpos++]=tree[level][i]; }else{//这里,suppose大于0的数划分到中位数的左边 if(suppose!=0){//这里的处理太巧妙了!帅气! suppose--; toLeft[level][i]++; tree[level+1][lpos++]=tree[level][i]; }else{//表示 tree[level+1][rpos++]=tree[level][i]; } } } build(level+1,left,mid); build(level+1,mid+1,right); } //在[left,right]数据中查询[qleft,qright]中第k大的数据 int query(int level,int left,int right,int qleft,int qright,int k){ if( qleft==qright) return tree[level][qleft]; int s;//代表[left,qleft)之间有多个个元素被分到左边 int ss;//[qleft, qright]内将被划分到左子树的元素数目 int mid=(left+right)>>1; if(left==qleft){ s=0; ss=toLeft[level][qright]; }else{ s=toLeft[level][qleft-1]; ss=toLeft[level][qright]-s; } int newl,newr; if(k<=ss){//查询左边 newl=left+s; newr=left+s+ss-1; return query(level+1,left,mid,newl,newr,k); }else{//查询右边 newl=mid-left+1+qleft-s; newr=mid-left+1+qright-s-ss; return query(level+1,mid+1,right,newl, newr,k - ss); } } int main(){ int n,m,cas=0; while(scanf("%d",&n)!=EOF){ cas++;printf("Case %d:\n",cas);        int i;         for(i=1;i<=n;i++){             scanf("%d",&tree[0][i]);             sorted[i]=tree[0][i];         }         sort(sorted+1,sorted+n+1);         build(0,1,n);         scanf("%d",&m);        int ql,qr,k;         for(i=0;i<m;i++){             scanf("%d %d",&ql,&qr); k=(qr-ql)/2+1;            printf("%d\n",query(0,1,n,ql,qr,k));         } } return 0; } 



对查找函数的解释补充

查找深度为h,在大区间[st,ed]中找小区间[s,e]中的第k元素。  再看看他是如何工作的。我们的想法是,先判断[s,e]中第k元素在[st,ed]的哪个子树中,然后找出对应的小区间和k,递归的进行查找,直到小区间的s=e为止。  那如何解决这个问题呢?这时候前面记录的进入左子树的元素个数就派上用场了。通过之前的记录可以知道,在区间[st,s-1]中有el[h,s-1]进入左子树,记它为l。同理区间[st,e]中有el[h,e]个数进去左子树,记它为r。所以,我们知道区间小区间[s,e]中有(r-l)个数进入左子树。那么如果(r-l)>=k,那么就在左子树中继续查找,否则就在右子树中继续查找。  接着解决查找的小区间的问题。  如果接下来要查找的是左子树,那么小区间应该是[st+([st,s-1]区间进入左子树的个数),st+([st,e]区间内进入左子树的个数)-1],即区间[st+l,st+r-1]。显然,这里k不用变。  如果接下来要查找的是右子树,那么小区间应该是[mid+([st,s-1]区间中进入右子树的个数),mid+([st,e]区间进入右子树的个数)-1]。即区间[mid+(s-st-l),mid+(e-st-r)]。显然,这里k要减去区间里已经进入左子树的个数,即k变为k-(r-l)。  于是递归继续查找直到s=e即可。
//在[left,right]数据中查询[qleft,qright]中第k大的数据int query(int level,int left,int right,int qleft,int qright,int k){    //在大区间内查询小区间的第k大的数    if( qleft==qright)        return tree[level][qleft];    int s;//代表[left,qleft)之间有多个个元素被分到左边    int ss;//[qleft, qright]内将被划分到左子树的元素数目    int mid=(left+right)>>1;    if(left==qleft){        s=0;        ss=toLeft[level][qright];    }else{        s=toLeft[level][qleft-1];        ss=toLeft[level][qright]-s;    }    int newl,newr;    if(k<=ss){//查询左边  缩小小区间继续在本区间查找第k个不用改变k的大小        newl=left+s;        newr=left+s+ss-1;        return query(level+1,left,mid,newl,newr,k);    }else{//查询右边 显然这里k要减去区间里已经进入左子树的个数ss        newl=mid-left+1+qleft-s;        newr=mid-left+1+qright-s-ss;        return query(level+1,mid+1,right,newl, newr,k-ss);    }}

 

 

有一个题目 用了上面模板RE  用了下面的确过了 蛋疼

http://acm.nbut.cn/Contest/view/id/53/problem/J.xhtml

宁波工程学院 网络赛

#include <iostream>  #include <string.h>  #include <stdio.h>  #include <algorithm>  #define maxn 100010  #define mid ((l+r)>>1)  using namespace std; int t[20][maxn],sum[20][maxn]; int as[maxn]; void build(int p,int l,int r) {     int lm=0,i,ls=l,rs=mid+1;     for(i=mid;i>=l;i--)     {         if(as[i]==as[mid]) lm++;         else break;     }     for(i=l;i<=r;i++)     {         if(i==l) sum[p][i]=0;         else sum[p][i]=sum[p][i-1];         if(t[p][i]==as[mid])         {             if(lm)             {                 lm--;                 sum[p][i]++;                 t[p+1][ls++]=t[p][i];             }             else t[p+1][rs++]=t[p][i];         }         else if(t[p][i]<as[mid])         {             sum[p][i]++;             t[p+1][ls++]=t[p][i];         }         else t[p+1][rs++]=t[p][i];     }     if(l==r) return;     build(p+1,l,mid);     build(p+1,mid+1,r); } int query(int p,int l,int r,int ql,int qr,int k) {     int s,ss;     if(l==r) return t[p][l];     if(ql==l) s=0,ss=sum[p][qr];     else s=sum[p][ql-1],ss=sum[p][qr]-s;     if(k<=ss) return query(p+1,l,mid,l+s,l+sum[p][qr]-1,k);     else return query(p+1,mid+1,r,mid+1-l+ql-s,mid+1-l+qr-sum[p][qr],k-ss); } int main() { //freopen("dd.txt","r",stdin); int i,n,m,j; while(scanf("%d%d",&n,&m)!=EOF){for(i=1;i<=n;i++) { scanf("%d",&as[i]); t[0][i]=as[i]; } sort(as+1,as+n+1); build(0,1,n); while(m--) { int l,r,k; scanf("%d%d%d",&l,&r,&k); int ans=query(0,1,n,l,r,k); printf("%d\n",ans); } }    return 0; } 


 



原创粉丝点击