HDU2665--Kth Number(划分树)

题目大意:给出一个数列,求区间第K小数

 

分析:划分树。就是基于快排原理的线段树。线段树的每一层都类似于一次快排的结果。

建树时,把小于as[mid]的数分到左边,大于as[mid]的数分到右边,相等的根据情况分到左右两边。同时,用一个sum[d][i]数组记录第d层前i个元素比as[mid]小的个数,以便于之后的查询操作时缩小区间以及k。

查询,代码注释蛮清楚的了。[L, R]表示要查询的区间。

int Query(int d, int l, int r, int L, int R, int k) {
    int s, ss; //s表示区间[l, L)有多少个元素比as[mid]小,ss表示区间[L, R]有多少个元素比as[mid]小
    int mid = (l+r)>>1;
    if(l == r) return tree[d][l];
    if(l == L) s = 0;       //特判。因为区间是左闭右开的
    else s = sum[d][L-1];
    ss = sum[d][R]-s;
    if(ss >= k) return Query(d+1, l, mid, l+s, l+s+ss-1, k);
    else return Query(d+1, mid+1, r, mid+1+L-l-s, mid+1+R-l-s-ss, k-ss);  //L-l-s表示这次Query的区间之前有多少个大于as[mid]的数分到了右边
}                                                                         //R-l-s-ss表示这次Query的区间以及它之前共有多少个大于as[mid]的数分到了右边



代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 111111;
int tree[20][maxn];     //每一层类似于一次快排的结果
int sum[20][maxn];      //sum[d][i]表示第d层前i个元素有多少个比as[mid]小,加上等于as[mid]的元素分到左区间的个数
int a[maxn], as[maxn];  //a数组表示原始数组,as数组表示排序后数组

void Build(int d, int l, int r) {
    int mid = (l+r)>>1;
    int lpos = l, rpos = mid+1;     //lpos记录放入下一层的左区间的位置,rpos则是记录右区间
    int lsame = mid-l+1;            //lsame记录有前半个区间有多少个数与as[mid]相等,初始时假设全都相等
    for(int i = l; i <= mid; i++)
        if(as[i] < as[mid]) lsame--;
    for(int i = l; i <= r; i++) {
        if(i == l) sum[d][i] = 0;
        else sum[d][i] = sum[d][i-1];
        if(tree[d][i] == as[mid]) {
            if(lsame) {             //lsame>0说明与as[mid]相同的元素还可以分到左区间
                lsame--;
                sum[d][i]++;
                tree[d+1][lpos++] = tree[d][i];
            }
            else tree[d+1][rpos++] = tree[d][i];
        }
        else if(tree[d][i] < as[mid]) {
            sum[d][i]++;
            tree[d+1][lpos++] = tree[d][i];
        }
        else tree[d+1][rpos++] = tree[d][i];
    }
    if(l != r) {
        Build(d+1, l, mid);
        Build(d+1, mid+1, r);
    }
}

int Query(int d, int l, int r, int L, int R, int k) {
    int s, ss; //s表示区间[l, L)有多少个元素比as[mid]小,ss表示区间[L, R]有多少个元素比as[mid]小
    int mid = (l+r)>>1;
    if(l == r) return tree[d][l];
    if(l == L) s = 0;       //特判。因为区间是左闭右开的
    else s = sum[d][L-1];
    ss = sum[d][R]-s;
    if(ss >= k) return Query(d+1, l, mid, l+s, l+s+ss-1, k);
    else return Query(d+1, mid+1, r, mid+1+L-l-s, mid+1+R-l-s-ss, k-ss);  //L-l-s表示这次Query的区间之前有多少个大于as[mid]的数分到了右边
}                                                                         //R-l-s-ss表示这次Query的区间以及它之前共有多少个大于as[mid]的数分到了右边

int main() {
    int T, n, m;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            tree[0][i] = as[i] = a[i];
        }
        sort(as+1, as+n+1);
        Build(0, 1, n);
        while(m--) {
            int L, R, k;
            scanf("%d%d%d", &L, &R, &k);
            printf("%d\n", Query(0, 1, n, L, R, k));
        }
    }
    return 0;
}


 

 

你可能感兴趣的:(HDU2665--Kth Number(划分树))