hdu 2655 (求区间K小数)

该博客介绍了一种利用划分树解决在给定区间内查找第K小数的方法。通过将未排序的数组排序,然后按照排序后的中间值进行划分,逐步缩小查询范围,最终找到目标值。在Query函数中,根据小于等于中间值的元素数量调整搜索区间,以逼近目标计数。
摘要由CSDN通过智能技术生成

题目


划分树吧,在这学得


记没排序的数组为UA(unsorted array,即原数组),排序数组SA(sorted array)

划分树的建树过程:将区间为[L,R]的UA,按SA[MID]来划分,如果比SA[MID]值小,则分到左子树,比其大,则分到右子树,并把在某个位置小于等于SA[MID]的个数记录下来,直到区间长度为1为止。

 

Query函数在做的一个工作将区间长度逐渐缩为1。以下以在[L,R]内找第CNT大的数为例说明。假设

S表示在[L,R]内有几个小于等于DATA[MID]的个数

SS表示在[tr[root].l,l-1]内有几个小于等于DATA[MID]的个数

 

当S>=CNT时,将L变为tr[root].l+SS,因为现在的S已经比CNT大了,还要SS那部分有什么用,

而将R变为tr[root].l+ss+s-1则相当于将下一次的S值减少1,以使S趋向于CNT

示意图:

POJ2104 <wbr>K-th <wbr>Number(划分树)

(注意:SS的值一定<=L-tr[root].l)

 

 

当S<CNT时,将L变为mid+(l-tr[root].l-ss)+1,括号内的数是为了进一步缩小区间长度用的。因为在[tr[root].l , l-1 ]内比DATA[MID]大的数,在下一步显然不需要

将R变为mid+l-tr[root].l-ss+(r-l+1-s),括号内的数是为了确定新区间的长度。即在[L,R]内有几个比DATA[MID]大的数。



#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

#define N 100010
int sa[N],tree[20][N],sum[20][N];
//sum[i][j]表示i层,1~j里比sa[m]小的数
int n,m,t,x,y,k;
struct node
{
    int l,r;
}root[N*4];
inline void build(int t,int d,int x,int y)
{
    root[t].l=x;
    root[t].r=y;
    if(x==y) return;
    int m=(x+y)>>1;
    int lless=m-x+1;
    for(int i=x;i<=y;i++)
    {
        if(tree[d][i]<sa[m]) lless--;
    }
    int lp=x,rp=m+1;
    for(int i=x;i<=y;i++)
    {
        if(i==x) sum[d][i]=0;
        else     sum[d][i]=sum[d][i-1];
        if(tree[d][i]<sa[m])
        {
            sum[d][i]++;
            tree[d+1][lp++]=tree[d][i];
        }
        else if(tree[d][i]>sa[m])
        {
            tree[d+1][rp++]=tree[d][i];
        }
        else
        {
            if(lless>0)
            {
                sum[d][i]++;
                lless--;
                tree[d+1][lp++]=tree[d][i];
            }
            else
            {
                tree[d+1][rp++]=tree[d][i];
            }
        }
    }
    build(t*2,d+1,x,m);
    build(t*2+1,d+1,m+1,y);
} 

inline int query(int t,int d,int x,int y,int k)
{
    if(x==y) return tree[d][x];
    int ss,s;
    int l=root[t].l,r=root[t].r,m=(l+r)>>1;
    //ss:[l,x)之间被分到左子树的,s:[x,y]被分到左子树的
    if(l==x)
    {
        ss=0;
        s=sum[d][y];
    }
    else
    {
        ss=sum[d][x-1];
        s=sum[d][y]-sum[d][x-1];
    }
    if(s>=k)
    {
        int xx=l+ss;//既然[x,y]的就够了,[l,x-1]就不用了里的ss个就没用了。
        int yy=l+ss+s-1;//将下一次的s减1
        return query(t*2,d+1,xx,yy,k);
    }
    else
    {
        int r1=x-l-ss;//[l,x-1]之间被分到右子树
        int r2=y-x+1-s;//[x,y]之间被分到右子树
        int xx=m+1 +r1;
        int yy=m+1 +r1+r2-1;
        return query(t*2+1,d+1,xx,yy,k-s);
    }
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(sum,0,sizeof(sum));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sa[i]);
            tree[0][i]=sa[i];
        }
        sort(sa+1,sa+n+1);
        build(1,0,1,n);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&k);
            printf("%d\n",query(1,0,x,y,k));
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值