POJ-2104 K-th Number(线段树[归并树]-区间第k大)

来源:互联网 发布:阿里云数据分析 编辑:程序博客网 时间:2024/05/22 06:57

题目链接

POJ-2104

题目大意

n个数,Q个询问,每个询问给一个区间[L,R]k,求区间[L,R]从小到大排完序之后的第k大的数是多大?

数据范围

1n1e51Q5000
1LiRin1kiRiLi+1

解题思路

对于这种经典的问题,解法有很多种,这里介绍的是基于线段树的解法,算个板子吧!
如果有一个排好序的数组,给一个x,直接二分一下就知道x是数组的第几大了。基于这种做法,把数列用线段树维护起来。这时线段树的每个节点保存的不再是某个数值,而是对应区间排好序之后的那个数列。
建立线段树的过程和归并排序类似(如果不了解归并排序,可以看看这篇blog:link),一个节点的数列就是其左右儿子的数列归并之后的结果。这个线段树几乎就是归并排序的再现,所以这样的线段树也叫归并树。
还是来个图,实在点。数列a[]=1,5,6,2,3,7,8,4,建出来的树就是这样:
这里写图片描述

接下来就是查询了。
- 如果要查询的区间和当前节点的区间完全没有交集,则返回0个。
- 如果要查询的区间完全包含了当前节点的区间,就像上面一样,对当前节点的数列二分一下就好。
- 否则,递归计算左右儿子,求和即可。


建树复杂度O(nlogn),询问里面一个logn,访问节点一个logn,二分节点数列又一个logn。总复杂度O(nlogn+Qlog3n)。空间复杂度O(n),有个常数。

代码:

#include <cstdio>#include <cstring>#include <cstdlib>#include <cmath>#include <algorithm>#include <queue>#include <set>#include <map>#include <stack>#include <vector>using namespace std;typedef long long LL;const int inf = 1 << 30;const LL INF = 1LL << 60;const int MaxN = 100010;int n, m;int a[MaxN + 5];vector <int> tree[4 * MaxN + 5];void Build(int rt, int l, int r) {    if(l == r) {        tree[rt].push_back(a[l]);        return;    }    int mid = (l + r) >> 1;    Build(rt << 1, l, mid);    Build(rt << 1 | 1, mid + 1, r);    //分配(r - l + 1)个元素所需的空间    tree[rt].resize(r - l + 1);    //用STL的merge函数将左右儿子的数列合并    merge(tree[rt << 1].begin(), tree[rt << 1].end(), tree[rt << 1 | 1].begin(), tree[rt << 1 | 1].end(), tree[rt].begin());}//计算区间[L, R]中不超过x的个数int query(int rt, int l, int r, int L, int R, int x) {    if(r < L || l > R) return 0;    else if(L <= l && r <= R) {        //二分当前节点的数列中有多少个不超过x的个数        return upper_bound(tree[rt].begin(), tree[rt].end(), x) - tree[rt].begin() ;    }    else {        //递归左右儿子        int mid = (l + r) >> 1;        int lc = query(rt << 1, l, mid, L, R, x);        int rc = query(rt << 1 | 1, mid + 1, r, L, R, x);        return lc + rc;    }}int main() {    while(scanf("%d %d", &n, &m) != EOF)    {        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);        Build(1, 1, n);        sort(a + 1, a + n + 1);        for(int i = 1; i <= m; i++) {            int L, R, k;            //求区间[L, R]中的第k个数            scanf("%d %d %d", &L, &R, &k);            int l = 1, r = n;            int mid = 0, res = 0;            while(l <= r) {                mid = (l + r) >> 1;                int ans = query(1, 1, n, L, R, a[mid]);                if(ans >= k) res = mid, r = mid - 1;                else l = mid + 1;            }            printf("%d\n", a[res]);        }    }    return 0;}
阅读全文
0 0