POJ 2104 K-th Number (线段树)

K-th Number
Time Limit: 20000MS   Memory Limit: 65536K
Total Submissions: 46589   Accepted: 15553
Case Time Limit: 2000MS

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

题意:

给定一个数列a1,a2,…,an和m个三元组表示的查询。对于每个查询(i,j,k),输出ai,ai+1,…,aj的升序排列中第k个数。

 

分析:

使用线段树来解决这个问题。我们把数列用线段树维护起来。线段树的每个节点都保存了对应区间排好序的结果。在这之前我们接触过的线段树节点上保存的都是数值,而这次则有所不同,每个节点保存了一个数列。

 POJ 2104 K-th Number (线段树)_第1张图片

建立线段树的过程和归并排序的类似,而每个节点的数列就是其两个儿子节点的数列合并后的结果。建树的复杂度是O(logn)。顺便一提,这棵树正是归并排序的完整再现。

要计算在某个区间中不超过x的数的个数,只需要递归地进行如下操作就可以了。

*如果所给的区间和当前节点的区间完全没有交集,那么返回0个。

*如果所给的区间完全包含了当前节点对应的区间,那么使用二分搜索法对该节点上保存的数组进行查找。

*否则对两个儿子递归地进行计算之后求和即可。

 

由于对应同一深度的节点最多只访问常数个,因此可以在O(log2n)时间里求出不超过x的数的个数。所以整个算法的复杂度是O(nlogn + mlog3n)。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#define maxn 100000
using namespace std;

vector<int> dat[4*maxn + 50];    //线段树的数据
int a[maxn + 50];
int n, q;

//构建线段树
//k是节点的编号,和区间[l, r)对应
void build(int k, int l, int r)
{
    if (r - l == 1) {
        dat[k].push_back(a[l]); return;
    }
    int lc = k << 1, rc = k << 1 | 1;
    build(lc, l, (l + r) / 2);
    build(rc, (l + r) / 2, r);
    dat[k].resize(r - l);
    //利用STL的merge函数把两个儿子的数列合并
    merge(dat[lc].begin(), dat[lc].end(), dat[rc].begin(), dat[rc].end(),dat[k].begin());
}

//计算[i, j)中不超过x的数的个数
//k是节点的编号,和区间[l, r)对应
int query(int i, int j, int x, int k, int l, int r)
{
    if (j <= l || r <= i)
        //完全不相交
        return 0;
    else if (i <= l&&r <= j){
        //完全包含在里面
        return upper_bound(dat[k].begin(), dat[k].end(), x) - dat[k].begin();
    }
    else {
        //对儿子递归地计算
        int lcnt = query(i, j, x, k << 1, l, (l + r) / 2);
        int rcnt = query(i, j, x, k << 1 | 1, (l + r) / 2, r);
        return lcnt + rcnt;
    }
}

int search(int x, int y, int k)
{
    int l = -1000000000 - 1;
    int r = -l + 2;
    while (l < r){
        int mid = (l + r) >> 1;
        int num = query(x, y+1, mid, 1, 1, n+1);
        if (k <= num) r = mid;
        else{
            l = mid + 1;
        }
    }
    return l;
}

int main()
{
    while (cin >> n >> q)
    {
        for (int i = 1; i <= n; i++){
            scanf("%d", a + i);
        }
        build(1, 1, n + 1);
        int li, ri, ki;
        for (int i = 0; i < q; i++){
            scanf("%d%d%d", &li, &ri, &ki);
            printf("%d\n", search(li, ri, ki));
        }
    }
    return 0;
}


你可能感兴趣的:(POJ 2104 K-th Number (线段树))