划分树

划分树就是简单求区间第K大的数是多少,但是要求序列是固定的,不能修改,否则又要重新建树,没有持久化的性质。感觉这个性价比不高,但是还是记录下自己学习的过程和理解吧。


划分树其实就是通过把序列建成二叉树进行操作,因为是求区间第K大,首先肯定要把原序列排好序。一个节点保存一段序列,这个节点的左儿子保存它前mid小的数字,但是相对位置不会发生改变,右儿子保存剩下的数字,同样相对位置不会发生改变。我们还需要一个数组cntleft[d][i]表示当前节点中第i个位置之前有多少个被分到左儿子中了。


例如一段序列为:2 5 6 3 5 8 1 9 (排序后的序列:1 2 3 5 5 6 8 9)它的左儿子序列就为: 2 5 3 1, 右孩子的序列为:6 5 8 9,cntleft数组也相应赋值。

接下来就是询问:

      假设当前节点所表示的区间为【L, R】,询问区间为【qL, qR】。我们可以通过cntleft数组知道在询问区间中有多少个数被分到左儿子中。

      假设有n个被分到左儿子中了,如果n>=k我们就要从左儿子中查询了,那新的查询区间应该是什么呢?newl = l+cntleft[ql-1]+cntleft[l-1], newr = newl+n-1;

      如果n < k,那我们就要从右儿子中查询,新的查询区间应该是:newr = qr+cntleft[r]-cntleft[qr], newl = newr-(qr-ql-n);

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define N 100002

using namespace std;

int tr[20][N], cntleft[20][N], val[N];

void build(int d, int l, int r)
{
     if (l == r) return;
     int i, same = 0, mid, lpos, rpos;
     mid = (l+r)>>1, lpos = l, rpos = mid+1;
     for (i = l;i <= r;i++)
     {
         if (tr[d][i] < val[mid])
            same++;
     }
     same = mid-l+1-same;
     for (i = l;i <= r;i++)
     {
         if (tr[d][i] < val[mid])
            tr[d+1][lpos++] = tr[d][i];
         else if (tr[d][i] == val[mid] && same)
              tr[d+1][lpos++] = tr[d][i],same--;
         else tr[d+1][rpos++] = tr[d][i];
         cntleft[d][i] = cntleft[d][l-1] + lpos-l;
     }
     build(d+1, l, mid);
     build(d+1, mid+1, r);
}

int query(int d, int l, int r, int ql, int qr, int k)//区间总长l,r,查询ql至qr到第k小数
{
    if (ql==qr)
        return tr[d][ql];
    int mid=(l+r)>>1;
    int temp=cntleft[d][qr]-cntleft[d][ql-1];
    if (temp>=k)
    {
        int newl=l+cntleft[d][ql-1]-cntleft[d][l-1];
        int newr=newl+temp-1;
        return query(d+1,l,mid,newl,newr,k);
    }
    else
    {
        int newr=qr+cntleft[d][r]-cntleft[d][qr];
        int newl=newr-(qr-ql-temp);
        return query(d+1,mid+1,r,newl,newr,k-temp);
    }
}


int main()
{
    int n, m, i, l, r, k;
    while (~scanf ("%d%d", &n, &m))
    {
          memset (cntleft, 0, sizeof (cntleft));
          for (i = 1;i <= n;i++)
          {
              scanf ("%d", &val[i]);
              tr[0][i] = val[i];
          }
          sort (val+1, val+n+1);
          build(0, 1, n);
          while (m--)
          {
                scanf ("%d%d%d", &l, &r, &k);
                printf ("%d\n", query(0, 1, n, l, r, k));
          }
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值