划分树

划分树的基本思想就是对于某个区间,把它划分成两个子区间左边区间的数小于右边区间的数。查找的时候通过记录进入左子树的数的个数,确定下一个查找区间,最后范围缩小到1,就找到了。

建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数sorted[mid]小于sorted[mid]的数划入他的左子树大于它的划入右子树。同时,对于第i个数,记录在[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间和右子树区间递归的继续建树就可以了。

tree[20][maxn]//表示每层每个位置的值

sorted[maxn]//已经排序好的数

!!!! toleft[20][maxn]//toleft[dep][i]表示第dep层区间[1,i]有多少个数划分到左子树

在这个过程中,数的相对位置不发生变化

sorted:  [1   2   3   4   5  6  7]

tree[0]:    [1   5   2   3   4   6  7]    topleft[0] [1,1,2,3,4,4,4]

tree[1]:    [1   2   3   4] [ 5   6  7]    topleft[1] [1,2,2,2,3,4,4]

tree[2]:    [1   2] [3   4] [5    6][0]   topleft[2] [1,1,2,2,3,3,0]

tree[3]:    [0] [0] [0] [0] [0]  [0] [0]  topleft[3] [0,0,0,0,0,0,0]

//hdu 2665
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
#include<map>
#include<vector>
using namespace std;
int n,m;
int tree[20][100010];
int sorted[100010];
int toleft[20][100010];
void build(int l,int r,int dep)
{
    if(l==r)//已经到达最后一层
        return;
    int mid=(l+r)>>1;
    int same=mid-l+1;//表示等于中间值且被分为左边的个数
    for(int i=l;i<=r;i++)
    {
        if(tree[dep][i]<sorted[mid])
            same--;
    }
    int lpos=l;
    int rpos=mid+1;
    for(int i=l;i<=r;i++)
    {
        if(tree[dep][i]<sorted[mid])
            tree[dep+1][lpos++]=tree[dep][i];
        else if(tree[dep][i]==sorted[mid]&&same>0)//看他是否被分到左边
        {
            tree[dep+1][lpos++]=tree[dep][i];
            same--;
        }
        else
            tree[dep+1][rpos++]=tree[dep][i];
        toleft[dep][i]=toleft[dep][l-1]+lpos-l;//记录区间被分到左边值的个数
    }
    build(l,mid,dep+1);
    build(mid+1,r,dep+1);
}
int query(int L,int R,int l,int r,int dep,int k)
{
    if(l==r)
        return tree[dep][l];
    int mid=(L+R)>>1;
    int cnt=toleft[dep][r]-toleft[dep][l-1];
    if(cnt>=k)//在左子树上
    {
        int newl=L+toleft[dep][l-1]-toleft[dep][L-1];//在往左右子树分配的时候相对位置是不会发生变化的,toleft[dep][l-1]-toleft[dep][L-1]不在区间内,就除去它们,得到新区间的左边界
        int newr=newl+cnt-1;//右边界
        return query(L,mid,newl,newr,dep+1,k);
    }
    else
    {
        int newr=r+toleft[dep][R]-toleft[dep][r];//toleft[dep][R]-toleft[dep][r]这一部分被分到了左区间,原来在r的右边现在到r的左边,所以r的值增大toleft[dep][R]-toleft[dep][r],得到新区间的右边界
        int newl=newr-(r-l-cnt);//左边界[l+cnt,r],[newl,newr],本来就是相同的区间,只不过是右边一部分数跑到了左边,是边界的位置发生了变化
        return query(mid+1,R,newl,newr,dep+1,k-cnt);
    }
}



int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(tree,0,sizeof(tree));
        memset(toleft,0,sizeof(toleft));
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&tree[0][i]);
            sorted[i]=tree[0][i];
        }
        sort(sorted+1,sorted+n+1);
        build(1,n,0);//建树
        int l,r,k;
        for(int i=0; i<m; i++)
        {
            scanf("%d%d%d",&l,&r,&k);
            printf("%d\n",query(1,n,l,r,0,k));
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值