划分树

划分树和归并树都是用线段树作为辅助的,原理是基于 快排 和 归并排序 的。

划分树的建树过程基本就是模拟快排过程,取一个已经排过序的区间中值,然后把小于中值的点放左边,大于的放右边。并且记录d层第i个数之前(包括i)小于中值的放在左边的数。


 
#include <iostream>

#include <cstdio>

#include <string.h>

#include <algorithm>

using namespace std;

#define M 100001

#define LL(x) (x<<1)

#define RR(x) (x<<1|1)

struct Seg_Tree

{

    int left,right;

    int mid()

    {

        return (left + right) >> 1;

    }

} tt[M<<2];

int len;

int sorted[M];

int toLeft[20][M];

int val[20][M];


void build(int l,int r,int d,int idx)

{

int i;

    tt[idx].left = l;

    tt[idx].right = r;

    if(tt[idx].left == tt[idx].right)   return ;

    int mid = tt[idx].mid();

    int lsame = mid - l + 1;//lsame表示和val_mid相等且分到左边的

    for(i = l ; i <= r ; i ++)

    {

        if(val[d][i] < sorted[mid])

        {

            lsame --;//先假设左边的数(mid - l + 1)个都等于val_mid,然后把实际上小于val_mid的减去

        }

    }

    int lpos = l;

    int rpos = mid+1;

    int same = 0;

    for(i = l ; i <= r ; i ++)

    {

        if(i == l)

        {

            toLeft[d][i] = 0;//toLeft[i]表示[ tt[idx].left , i ]区域里有多少个数分到左边

        }

        else

        {

            toLeft[d][i] = toLeft[d][i-1];

        }

        if(val[d][i] < sorted[mid])

        {

            toLeft[d][i] ++;

            val[d+1][lpos++] = val[d][i];

        }

        else if(val[d][i] > sorted[mid])

        {

            val[d+1][rpos++] = val[d][i];

        }

        else

        {

            if(same < lsame)  //有lsame的数是分到左边的

            {

                same ++;

                toLeft[d][i] ++;

                val[d+1][lpos++] = val[d][i];

            }

            else

            {

                val[d+1][rpos++] = val[d][i];

            }

        }

    }

    build(l,mid,d+1,LL(idx));

    build(mid+1,r,d+1,RR(idx));

}


int query(int l,int r,int k,int d,int idx)

{

    if(l == r)

    {

        return val[d][l];

    }

    int s;//s表示[ l , r ]有多少个分到左边

    int ss;//ss表示 [tt[idx].left , l-1 ]有多少个分到左边

    if(l == tt[idx].left)

    {

        s = toLeft[d][r];

        ss = 0;

    }

    else

    {

        s = toLeft[d][r] - toLeft[d][l-1];

        ss = toLeft[d][l-1];

    }

    if(s >= k)  //有多于k个分到左边,显然去左儿子区间找第k个

    {

        int newl = tt[idx].left + ss;

        int newr = tt[idx].left + ss + s - 1;//计算出新的映射区间

        return query(newl,newr,k,d+1,LL(idx));

    }

    else

    {

        int mid = tt[idx].mid();

        int bb = l - tt[idx].left - ss;//bb表示 [tt[idx].left , l-1 ]有多少个分到右边

        int b = r - l + 1 - s;//b表示 [l , r]有多少个分到右边

        int newl = mid + bb + 1;

        int newr = mid + bb + b;

        return query(newl,newr,k-s,d+1,RR(idx));

    }

}

int main(){

int n,m,i;

int a,b,k;

while(cin>>n>>m){

for(i=1;i<=n;i++){

cin>>val[0][i];

sorted[i]=val[0][i];

}

sort(sorted+1,sorted+1+n);

build(1,n,0,1);

while(m--){

cin>>a>>b>>k;

cout<<query(a,b,k,0,1)<<endl;

}

}

return 0;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值