poj 2104 划分树(查询区间第k大数)

题意:给定一个n个数的数组,有m个查询,每个查询为[a,b,k],意思是区间[a,b]的第k大数。

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

建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数a[mid],小于a[mid]的数划入他的左子树[l,mid-1],大于它的划入右子树[mid,r]。同时,对于第i个数,记录在[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间[l,mid-1]和右子树区间[mid,r]递归的继续建树就可以了。
建树的时候要注意对于被分到同一子树的元素,元素间的相对位置不能改变。

查找的过程:查找深度为h,在大区间[L,R]中找小区间[left,right]中的第k元素。再看看他是如何工作的。我们的想法是,先判断[left,right]中第k元素在[L,R]的哪个子树中,然后找出对应的小区间和k,递归的进行查找,直到小区间的left = right为止。具体思路见代码中注释。

注意:num数组除了这种建立方法还可以在建树时以每个区间为单位建立(百度百科的方法),

if( i==l )
num[c][i]=0;
else
num[c][i]=num[c][i-1];

这样建树在查询的时候代码能少写一些。

#include <cstdio>
#include <algorithm>
#define N 100005
using namespace std;
int s[N],t[25][N],num[25][N];
int n,m;
void build(int left,int right,int d){
    int i,j,k,mid,ml,flag;
    if(left == right)
        return;
    mid = (left+right)>>1;
    ml = mid-left+1;
    for(i = left;i<=right;i++)//注意是从left到right而不是到mid
        if(t[d][i] < s[mid])
            ml--;
    j = left,k = mid+1;
    for(i = left;i<=right;i++){
        flag = 0;
        if(t[d][i] < s[mid] || (t[d][i]==s[mid]&&ml)){//表示进入左子树
            flag = 1;
            if(t[d][i] == s[mid])//如果是和中位数相等的情况
                ml--;
            t[d+1][j++] = t[d][i];
        }else
            t[d+1][k++] = t[d][i];
        num[d][i] = num[d][i-1]+flag;//记录进入左子树的个数
    }
    build(left, mid, d+1);
    build(mid+1, right, d+1);
}
int query(int left,int right,int k,int L,int R,int d){
    int mid,x,y,rx,ry,val;
    if(left == right)
        return t[d][left];
    mid = (L+R)>>1;
    x = num[d][left-1] - num[d][L-1];//[L,left)内去往左子树的个数
    y = num[d][right] - num[d][L-1];//[L,right]内去往左子树的个数
    val = y-x;//[left,right]内去往左子树的个数
    rx = left - L - x;//[L,left)内去往右子树的个数
    ry = right - L + 1 - y;//[L,right]内去往右子树的个数
    if(k <= val)
        return query(L+x, L+y-1, k, L, mid, d+1);
    return query(mid+1+rx, mid+ry, k-val, mid+1, R, d+1);
}
int main(){
    int i,j,k,x;
    scanf("%d %d",&n,&m);
    for(i = 1;i<=n;i++){
        scanf("%d",&s[i]);
        t[0][i] = s[i];
    }
    sort(s+1,s+1+n);
    build(1,n,0);
    for(i = 0;i<m;i++){
        scanf("%d %d %d",&j,&k,&x);
        printf("%d\n",query(j,k,x,1,n,0));
    }
}
/*
t数组值如下,四周序号表示下标
t 1 2 3 4 5 6 7
0 1 5 2 6 3 7 4
1 1 2 3 4 5 6 7
2 1 2 3 4 5 6 7
3 1 2 3 4 5 6 0
 
num数组值如下,四周序号表示下标
num 1 2 3 4 5 6 7
  0 1 1 2 2 3 3 4
  1 1 2 2 2 3 4 4
  2 1 1 2 2 3 3 0
*/


你可能感兴趣的:(poj 2104 划分树(查询区间第k大数))