POJ 2104:K-th Number (划分树)

点击打开题目链接

题目意思,给出n和m,然后第二行给出n个数,接下来有m个循环,输入a,b,k,询问a,b这个区间中的第k小值是谁。

看到后也没思路,网上百度了一下,用划分树、主席树去做,这两个东西只听说过,但是没有用过,昨天2017多校

联合求区间第k大树,中间实在做不动了(绍兴中学毕业生出的题可真难,ORZ),所以就去学习划分树了,虽然多

校那个题目是可以模拟过的,划分树应该怼不过,因为那个题目求所以满足区间长度大于k的区间中的第k大值,区间

组合比POJ这个题目多许多,不过还是要学习的,看了一上午,题都看错了,老是想着求第k大数,结果看了代码看不

懂,才发现POJ这个题让求第k小树。这是我的第一道划分树的题目,主席树还没学。


其基本的实现方法就是快排的思想加上线段树的操作。可以先看完代码再来看这个讲解。

划分树的创建过程是这样的,对于给出的n个数字,我们开设一个数组sorted来存放这些数,并进行sort排序,则sorted

数组中存放的就是n个数已经排好序的序列。

1.则对于n个数,建树过程如下。

对于区间[left,right],我们计算区间中点mid,则我们把小于sorted[mid]的数字放到左子树去,把大于sorted[mid]的数放到右

子树去,这样的话,我们就知道,左子树放的都是较小的数,右子树放置的是较大的数,那么和sorted[mid]相等的数怎么

放呢,我们这样处理,对于相等的数,如果左子树还有位置用来放数,我们就放到左子树里面,否则我们就放到右子树里面。

我们并可以记录从左边界到当前位置扫描过的数字有多少将来要放到左子树,记录数据用total_left数组。

total[depth][i]代表第depth层,从left开始到i这个位置有多少个数放置于左子树。

然后我们反复在左子树和右子树中进行上述过程。

2.查询a,b区间的第k小值。

假如当前区间为[left,right],首先计算在当前区间的左断点left到a左侧共有多少个数进入到左子树,假如设设个数为lx,由于

这个区间是[left~a-1]因此这lx数并不在我们要查找的区间里面。

我们也可以计算出left~b这段区间中有多少个数字会进入左子树,我们把它记为ly。

根据这两个数,我们计算出我们要查询的区间中有多少个数位于左子树。很容易得到就是 ly-lx 个。

我们还可以计算出【left~a-1】这个区间有多少个数进入右子树,首先left~a-1这个区间共有a-left个数,已经知道了这个区间中

有lx个数进入左子树,则a-left-lx就是这个区间中进入右子树的数字个数,我们把它记作 rx , rx = a-left-lx。当然我们知道这rx

个数也不再我们要查找的区间[a,b]内。

我们还可以计算出【left~b】这个区间中有多少个数会进入右子树,首先left~b这个区间中共有b-left+1个数,ly是这个区间中有

多少个数位于左子树,那么b-left+1-ly,就是有多少数会进入右子树,即这个数为ry = b-left+1-ly.

我们计算这么多,lx,ly,rx,ry有啥用呢, 主要就是为了改变我们要查询的区间。ly-lx表示[a~b]这个区间中的数有多少个位于左子树,

如果ly-lx大于等于k了,则[a,b]内的第k小数一定在左子树中,那么怎么确定区间呢,我们已经知道在left~a-1中有 lx数是在左子树的

在建树的时候,放置数字的时候是在下一层以left为开始的地方放置的,由于left~a-1的lx个数会按照左边界left由左到右摆放,则

左边的left~left+lx-1,这些数都是不是区间[a,b]内的数,那么在左子树查找就可以从left+lx开始查找,右边界则是left+ly-1.

如果ly-lx小于k,则当前较小的数还不足k个,那么我们要到右子树中查找,由于left~a-1中的位于右子树的树有rx个,因此

在左子树从mid+1到mid+1+rx-1这里的数也都是不在区间 [a,b]中的,我们不用考虑,则右子树查询区间从mid+1+rx开始,

查询区间的有边界是mid+1+ry-1。每次递归这找就行了。


详情请见代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>

using namespace std;

/**求解区间第k小值**/
const int maxn = 100050;  ///数据规模
int sorted[maxn];         ///存放已经排好序的数
int tree[21][maxn];
int total_left[21][maxn]; ///

void build(int left,int right,int depth)
{
    if(left == right) return;
    int mid = (left+right)>>1;
    int same = mid-left+1; ///位于左子树的数据
    ///计算在左子树中于sort[mid]相等的数据的个数,左子树same个位置都可以放置于sort[mid]值相等的数
    for(int i = left; i <= right; i++)
    {
        if(tree[depth][i] < sorted[mid])
            same--;
    }
    ///接下来吧小于sorted[mid]的放在左子树,把大于sorted[mid]的放在右子树,相等的放左放右视剩余位置而定。
    int ls = left;  ///左子树放置比sorted[mid]小的数的起点
    int rs = mid+1; ///右子树放置比sorted[mid]大的数的起点
    for(int i = left; i <= right; i++)
    {
        /**如果第i个位置上的值比sorted[mid]小,直接放入左子树,或者是相等关系,就需要看same值代表左区间
        中剩余的空位个数,如果空位个数大于0,则放置到左子树,否则放置到右子树。
        **/
        int num = 0;
        if(tree[depth][i]<sorted[mid] || (tree[depth][i]==sorted[mid] && same>0))
        {
            num = 1;
            tree[depth+1][ls++] = tree[depth][i];
            if(tree[depth][i]==sorted[mid])
                same--;
        }
        else ///放入右子树
        {
            tree[depth+1][rs++] = tree[depth][i];
        }
        total_left[depth][i] = total_left[depth][i-1]+num;  ///统计left到当前位置i总共多少个数放入左区间。
    }
    build(left,mid,depth+1);           ///递归构建左子树
    build(mid+1,right,depth+1);        ///递归构建右子树
}
///查询区间a,b内的第k小值
int query(int a,int b,int k,int left,int right,int depth)
{
    if(a == b)
        return tree[depth][a];
    int mid = (left+right)>>1;
    int lx = total_left[depth][a-1] - total_left[depth][left-1];
    /**求left~a-1这个区域内放置于左子树中的数字的个数,这些数字从左子树的左边界开始依次往右放置,
    这lx个数不在我们要查询的范围内**/
    int ly = total_left[depth][b] - total_left[depth][left-1];
    /**求left~b这个区域内放置于左子树中的数字的个数。**/
    int ry = b-left+1-ly;  ///求left~b范围内,位于右子树的数的个数
    int rx = a-left-lx;
    /**a-left是left~a-1这个区域中数的个数,减去这个区域中位于左子树的数字个数,就是这个区域中位于
    右子树中的数字个数**/
    int cnt = ly-lx;  ///计算a,b范围内,有多少个数位于左子树
    /**已经知道放入左子树的数都是较小的数,如果其个数大于等于k,则第k小数必然在左区间**/
    if(cnt>=k)
    {
        return query(left+lx,left+ly-1,k,left,mid,depth+1);
    }
    else
    {
        return query(mid+1+rx,mid+1+ry-1,k-cnt,mid+1,right,depth+1);
    }
}
int main()
{
    int n,m,a,b,k;
    while(~scanf("%d%d",&n,&m))
    {
       for(int i = 1; i <= n; i++)
       {
           scanf("%d",&tree[0][i]);
           sorted[i] = tree[0][i];
       }
       sort(sorted+1,sorted+1+n);
       memset(total_left,0,sizeof(total_left));
       build(1,n,0);
       while(m--)
       {
           scanf("%d%d%d",&a,&b,&k);
           int ans = query(a,b,k,1,n,0);
           printf("%d\n",ans);
       }
    }
    return 0;
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值