划分树

划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大或第k小值 。

树的结构:

int sorted[N];    // 对原来集合中的元素排序后的值。   

struct tree{    int val[N];       // val 记录第 k 层当前位置的元素的值   

 int num[N];      // num 记录元素所在区间的当前位置之前进入左孩子的个数     

    }t[20];  


计算3到9区间的第二小值:

首先[3,9]区间之间有3个在左子树比k值大,所以跳到左子树中(大区间),小区间就变成[2,4],然后递归找到区间只剩一个值为止。

怎么找到的:

首先根据num数组我能知道[3,9]之间有多少个数进入了左子树。这样根据这个数来判断我们要的值是在左还是右。这样大区间就确定了。

然后,小区间的2是根据[1,3)之间有多少个进入左子树(值为1)的加上大区间的左端点(值为1);

小区间的右端点(4)是让小区间左端点(2)+[3,9]之间有多少个数进入左子树(有3个)-1;

现在我们判断如果是右子树的话。

比如是找第4小值:

首先[3,9]之间有3个在左子树比k小,所以跳到右子树。小区间是[7,10]

小区间的7是mid(5)(中间的一个数)+1+小区间的3-大区间的1(这个是计算区间之间右多少个数)-1([1,3)之间进入左子树个数);

小区间的10是7+小区间的右端点9-小区间的左端点3+1(这个计算出小区间有多少数)-3(左子树进入了多少个小区间内的数);最后将k-3(左子树进入了多少个小区间内的数);

  1. #include <cstdio>  
  2. #include <iostream>  
  3. #include <cstring>  
  4. #include <queue>  
  5. #include <cmath>  
  6. #include <algorithm>  
  7. using namespace std;  
  8. const int N=100005;  
  9. int sorted[N];    // 对原来集合中的元素排序后的值。   
  10. int arr[N];  
  11. double Sum=0;  
  12. struct tree{  
  13.     int val[N];       // val 记录第 k 层当前位置的元素的值   
  14.     int num[N];      // num 记录元素所在区间的当前位置之前进入左孩子的个数   
  15. }t[20];  
  16. //划分树构建   
  17. void build(int l,int r,int p){  
  18.     if(l==r) return ;  
  19.     /* same 用来标记和中间值 sorted[mid] 相等的,且分到左孩子的数的个数。  
  20.     初始时,假定当前区间[lft,rht]有 mid-lft+1 个和 sorted[mid] 相等。     
  21.     先踢掉比中间值小的,剩下的就是要插入到左边的 
  22.     例如 1 3 3 3 3 5 5 7   same=4,sorted[mid]=3,分到左孩子且值为3的个数为3  
  23.     */     
  24.     int mid=(l+r)>>1,same=mid-l+1,lp=l,rp=mid+1;  
  25.     for(int i=l;i<=r;i++){  
  26.         if(t[p].val[i]<sorted[mid]) same--;  
  27.     }  
  28.     for(int i=l;i<=r;i++){  
  29.         if(i==l){                                  // 初始一个子树。   
  30.             t[p].num[i]=t[p].sum[i]=0;  
  31.         }  
  32.         else{                                     // 初始区间下一个节点。   
  33.             t[p].num[i]=t[p].num[i-1];  
  34.         }  
  35.         /* 如果大于,肯定进入右孩子,否则,判断是否还有相等的应该进入左孩子的,   
  36.          没有,就直接进入右孩子,否则进入左孩子,同时更新节点的 num 域*/     
  37.         if(t[p].val[i]<sorted[mid]){  
  38.             t[p].num[i]++;  
  39.             t[p+1].val[lp++]=t[p].val[i];  
  40.         }  
  41.         else if(t[p].val[i]>sorted[mid]){  
  42.             t[p+1].val[rp++]=t[p].val[i];  
  43.         }  
  44.         else{  
  45.             if(same){  
  46.                 same--;  
  47.                 t[p].num[i]++;  
  48.                 t[p+1].val[lp++]=t[p].val[i];  
  49.             }  
  50.             else  
  51.                 t[p+1].val[rp++]=t[p].val[i];  
  52.         }  
  53.     }  
  54.     build(l,mid,p+1);  
  55.     build(mid+1,r,p+1);  
  56. }   
  57. //划分树查找   
  58. /* 在区间[a, b]上查找第 k 小元素。*/   
  59. int query(int a,int b,int l,int r,int p,int k){  
  60.     int s;                      //[l, a)内将被划分到左子树的元素数目  
  61.     int ss;                     //[a, b]内将被划分到左子树的元素数目    
  62.     int mid=(l+r)>>1;  
  63.     if(l==r) return t[p].val[a];  
  64.     //区间端点点重合的情况,要单独考虑 !!!!!!  
  65.     if(a==l){  
  66.         s=0;  
  67.         ss=t[p].num[b];  
  68.     }  
  69.     else{  
  70.         s=t[p].num[a-1];  
  71.         ss=t[p].num[b]-s;        
  72.     }  
  73.      // 进入左孩子,同时更新区间端点值   
  74.     if(ss>=k){  
  75.         int la=l+s;  
  76.         int lb=l+s+ss-1;  
  77.         return query(la,lb,l,mid,p+1,k);  
  78.     }  
  79.     else{  
  80.         int la=mid+1+a-l-s;  
  81.         int lb=mid+1+b-l-s-ss;   //lb=la+b-a-num[b]=mid+1+a-l-s+b-a-s-ss  
  82.         return query(la,lb,mid+1,r,p+1,k-ss);  
  83.     }  
  84. }  
  85. int main() {  
  86.   
  87.     #ifndef ONLINE_JUDGE  
  88.     freopen("in.txt","r",stdin);  
  89.     #endif  
  90.     int n,m,k,a,b;  
  91.     while(~scanf("%d%d",&n,&m)){  
  92.         for(int i=1;i<=n;i++){  
  93.             scanf("%d",&arr[i]);  
  94.             t[0].val[i]=sorted[i]=arr[i];  
  95.         }  
  96.         sort(sorted+1,sorted+n+1);  
  97.         build(1,n,0);  
  98.         while(m--){  
  99.             scanf("%d%d%d",&a,&b,&k);  
  100.             printf("%d\n",query(a,b,1,n,0,k));  
  101.         }  
  102.     }  
  103. }              
题目:poj2104


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值