HDU2665--Kth Number(划分树)

来源:互联网 发布:qq点亮图标软件 编辑:程序博客网 时间:2024/06/04 19:48

题目大意:给出一个数列,求区间第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;}


 

 

0 0