划分树

划分树

建树:

建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数a[mid],小于a[mid]的数划入它的左子树[l,mid-1],大于它的划入右子树[mid,r]。
同时,对于第i个数a[i],记录在[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间[l,mid-1]和右子树区间[mid,r]递归的继续建树就可以了。
建树的时候要注意,对于被分到同一子树的元素,元素间的相对位置不能改变。

查找的过程中主要问题就是确定将要查找的区间。
查找深度为dep,在大区间[L ,R]中找小区间[l ,r]中的第k元素。
我们的想法是,先判断[l ,r]中第k元素在[L ,R]的哪个子树中,然后找出对应的小区间和k,递归的进行查找,直到小区间的l==r为止。
通过之前的记录可以知道,在区间[L,l-1]中有(toleft[dep][l-1]-toleft[dep][L-1])进入左子树,
记它为x。

同理区间[L,r]中有(toleft[dep][r]-toleft[dep][L-1])个数进去左子树,记它为y。
所以,我们知道区间小区间[l,r]中有(y-x)个数进入左子树。那么如果(y-x)>=k,那么就在左子树中继续查找,否则就在右子树中继续查找。

  接着,解决查找的小区间的问题。
  如果接下来要查找的是左子树,那么小区间应该是[L+([L,l-1]区间进入左子树的个数),L+([L,r]区间内进入左子树的个数)-1]。即区间[L+x,L+y-1]。
    显然,这里k不用变。
  如果接下来要查找的是右子树,那么小区间应该是[mid+([L,l-1]区间中进入右子树的个数),mid+([L,r]区间进入右子树的个数)-1]。
    即区间[mid+(l-L-x),mid+(r-L-y)]。
    显然,这里k要减去区间里已经进入左子树的个数,即k变为k-(y-x)。

模板以poj2761为例。

代码如下:

<pre name="code" class="cpp">#include <iostream>
#include <cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include <algorithm>
const int maxn = 100005;
using namespace std;

int tree[22][maxn], toleft[22][maxn];
int sorted[maxn];
void build(int left, int right, int deep)
{
    if(left == right) return ;
    int mid = (left+right)>>1;
    int same = mid - left +1;
    for(int i = left; i<=right; i++){
        if(tree[deep][i] < sorted[mid])
            same--;
    }

    int ls = left;
    int rs = mid+1;

    for(int i = left; i<=right; i++){
        int flag = 0;
        if(tree[deep][i]<sorted[mid] || tree[deep][i]==sorted[mid] && same>0){
            flag = 1;
            tree[deep+1][ls++] = tree[deep][i];
            if(tree[deep][i] == sorted[mid]) same--;
        }
        else{
            tree[deep+1][rs++] = tree[deep][i];
        }
        toleft[deep][i] = toleft[deep][i-1]+flag;
    }
    build(left, mid, deep+1);
    build(mid+1, right, deep+1);
}

int query(int left, int right, int k, int L, int R, int deep)
{
    if(left == right) return tree[deep][left];
    int mid = (L+R)>>1;
    int x = toleft[deep][left-1] - toleft[deep][L-1];
    int y = toleft[deep][right] - toleft[deep][L-1];
    int ry = right-L-y;
    int cnt = y-x;
    int rx = left-L-x;
    if(cnt>=k)
        return query(L+x,L+y-1,k,L,mid,deep+1);
    else
        return query(mid+rx+1,mid+1+ry,k-cnt,mid+1,R,deep+1);
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &n, &m)){
        for(int i = 1; i<=n; i++){
            scanf("%d", &sorted[i]);
            tree[0][i] = sorted[i];
        }
        sort(sorted+1, sorted+1+n);
        build(1, n, 0);
        while(m--){
            int left, right, k;
            scanf("%d%d%d", &left, &right, &k);
            printf("%d\n", query(left, right, k, 1, n, 0));
        }
    }
    return 0;
}


 相关的类似题目还有: 

poj2104
POJ 2104 K-th Number
大意:有n个数字排成一列,有m个询问,格式为:
left right k
即问在区间[left,right]第k大的数据为多少?

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN = 100000+10;
int tree[22][MAXN],toleft[22][MAXN];
int sorted[MAXN];//已经排好序的数据
void build_tree(int left,int right,int deep)//建树
{
 if(left==right)return;
 int mid = (left+right)>>1;
 int i;
 int same = mid-left+1;//位于左子树的数据
 for(i=left;i<=right;i++)  //计算放于左子树中与中位数相等的数字个数
 {
  if(tree[deep][i]<sorted[mid])
   same--;
 }

 int ls = left;
 int rs = mid+1;
 for(i=left;i<=right;i++)
 {
  int flag = 0;
  if((tree[deep][i]<sorted[mid])||(tree[deep][i]==sorted[mid]&&same>0))
  {
   flag = 1;
   tree[deep+1][ls++] = tree[deep][i];
   if(tree[deep][i]==sorted[mid])same--;
  }
  else
  {
   tree[deep+1][rs++] = tree[deep][i];
  }
      
  toleft[deep][i]=toleft[deep][i-1]+flag;
 }

 build_tree(left,mid,deep+1);
 build_tree(mid+1,right,deep+1);
}

int query(int left,int right,int k,int L,int R,int deep)
{
 if(left==right)return tree[deep][left];
    int mid = (L+R)>>1;
 int x = toleft[deep][left-1]-toleft[deep][L-1];//位于left左边的放于左子树中的数字个数
 int y = toleft[deep][right] - toleft[deep][L-1];//到right为止位于左子树的个数
 int ry = right-L-y;//到right右边为止位于右子树的数字个数
 int cnt = y-x;//[left,right]区间内放到左子树中的个数
 int rx = left-L-x;//left左边放在右子树中的数字个数
 if(cnt>=k)
  return query(L+x,L+y-1,k,L,mid,deep+1);
 else
  return query(mid+rx+1,mid+1+ry,k-cnt,mid+1,R,deep+1);

}

int main()
{
 int n,m;
 while(scanf("%d%d",&n,&m) == 2)
 {
  int i;
  
  for(i=1;i<=n;i++)
  {
   scanf("%d",&sorted[i]);
   tree[0][i] = sorted[i];
  }

  sort(sorted+1,sorted+n+1);
  build_tree(1,n,0);
  while(m--)
  {
   int left,right,k;
   scanf("%d%d%d",&left,&right,&k);
   printf("%d\n",query(left,right,k,1,n,0));
  }
 }
 return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值