划分树

划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值

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

划分树定义为,她的每一个节点保存区间   [lft, rht] 所有元素,元素排列顺序与原数组(输入)相同,但是,
两个子树的元素为该节点所有元素排序后( rht − lft + 1  )/ 2个进入左子树,其余的到右子树,同时维护一个
num域,num[i]  表示lft → i  这些点有多少进入了左子树。(摘自某牛人的博客) 

划分树的建立和查找简单举例:

在 [ 2 4 3 5 8 1 7 6 ] 中找区间[2,7]的第2小

①                 [ 2  4 3 5 8 1 7  6 ]                  // 原数组   [2,7]找第2小
②       [ 2  4 3 1  ]             [ 5 8 7 6 ]         // 分成左右两组   知道[2,7]中有3个到左子树   故在答案左
③   [ 2 1 ]    [  4 3  ]      [ 5 6 ]   [ 8 7 ]    // 同理   只有1个到左子树   故答案在右子树

④  [1] [2]   [3] [4]    [5] [6]  [7] [8]   // 一直迭代直到找到答案


/// 划分树入门
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;


const int maxn=100000+123;
int sorted[maxn];

struct node{
    int v[maxn], num[maxn];
}T[21];

void build(int l, int r, int p)
{/// 生成当前层的num以及下一层的v
    if(l==r)return;
    int mid=(l+r)>>1;
    int isame=mid+1-l, same=0;/// 貌似没用。。。其实很有用。。。
    for (int i=l; i<=r; ++i)
        if(T[p].v[i]<sorted[mid])same++;
    int ll=l, rr=mid+1;
    for (int i=l; i<=r; ++i)
    {
        if(i==l)/// 左端初始为0
        {
            T[p].num[i]=0;
        }else /// initialization
        {
            T[p].num[i]=T[p].num[i-1];
        }

        if(T[p].v[i]<sorted[mid])
        {
            T[p].num[i]++;
            T[p+1].v[ll++]=T[p].v[i];
        }else
        if(T[p].v[i]>sorted[mid])
        {
            T[p+1].v[rr++]=T[p].v[i];
        }else
        {
            if(same<isame)
            {
                same++;
                T[p].num[i]++;
                T[p+1].v[ll++]=T[p].v[i];
            }else
            {
                T[p+1].v[rr++]=T[p].v[i];
            }
        }
    }
    build(l, mid, p+1);
    build(mid+1, r, p+1);
}

int Query(int a, int b, int k, int p, int l, int r)
{
    if(l==r)return T[p].v[a];
    int la=(a==l)?0:T[p].num[a-1], mid=(l+r)>>1;///区间内a之前的左子树元素个数
//    printf("Q:%d %d\n", p, la);
    int s=T[p].num[b]-la;/// 【a,b】间的左子树元素个数
    if(s>=k)/// 左子树元素大于所要找的第k个元素的时候
    {
        a=l+la;
        b=l+la+s-1;
//        printf("l:a==%d b==%d l==%d r==%d  k=%d\n", a, b, l, mid, k);
        return Query(a, b, k, p+1, l, mid);
    }else
    {
        a=mid+a-l-la+1;
        b=b+mid+a-l-la-a+1-s;
//        printf("r:a==%d b==%d l==%d r==%d  k=%d\n", a, b, mid+1, r, k-s);
        return Query(a, b, k-s, p+1, mid+1, r);
    }
}

int main ()
{
    int n;
    int cas=0;
    while (~scanf("%d", &n))
    {
        printf("Case %d:\n", ++cas);
        for (int i=0; i<n; ++i)
        {
            scanf("%d", &T[0].v[i]);
            sorted[i]=T[0].v[i];
        }
        sort(sorted, sorted+n);
        build(0, n-1, 0);
//        for (int i=0; i<5; ++i)
//        {
//            for (int j=0; j<n; ++j)
//            {
//                printf("v=%d\t", T[i].v[j]);
//            }
//            puts("");
//            for (int j=0; j<n; ++j)
//                printf("m=%d\t", T[i].num[j]);
//            puts("");
//        }

        int m; scanf("%d", &m);
        for (int i=0; i<m; ++i)
        {
            int a, b; scanf("%d%d", &a, &b);
            printf("%d\n", Query(a-1, b-1, (b-a)/2+1, 0, 0, n-1));
        }
    }
    return 0;
}
/*
5
5 3 2 4 1
3
1 3
2 4
3 5
5
10 6 4 8 2
3
1 3
2 4
3 5
8
1 5 6 3 8 4 4 2
3
1 7
2 6
3 5

*/


你可能感兴趣的:(划分树)