首页 > 其他 > 详细

POJ 2014.K-th Number 区间第k大 (归并树)

时间:2017-07-11 22:19:32      阅读:163      评论:0      收藏:0      [点我收藏+]
Time Limit: 20000MS   Memory Limit: 65536K
Total Submissions: 57543   Accepted: 19893
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 109 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.
题目链接:http://poj.org/problem?id=2104
题意:找出区间第k大。
思路:
如果x是区间第k个数,那么一定有在区间中有不超过x的数不少于k个。
因此如果可以快速求出区间里不超过x的数的个数,就可以通过对所有数进行二分搜索得到的x来求出第k个数。
接下来,如何计算在某个区间里不超过x个数的个数。如果不进行预处理,只能便利一遍所有的元素。
另一方面,如果区间是有序的,那么就可以通过二分搜索高效的求出不超过x的数的个数了。但是,如果对于每个查询都分别做一次排序,就完全无法降低复杂度。
所以考虑使用分块和线段树进行求解。
线段树每个节点都保存了对应区间排序之后的结果。建立线段树的过程和归并排序类似,而每个节点的数列就是其两个儿子节点的数列合并后的结果。建树的复杂度是O(nlogn)。这棵线段树就是归并排序的完整再现,叫做归并树。
要计算某个区间中不超过x的个数,只需要递归的进行操作,直至所给定的区间完全包含当前节点区间,然后二分搜索该节点上保存的数列。
因此可以在O(long2n)时间里求出不超过x的数的个数。所以整个算法的复杂度是O(nlogn+mlong3n)。
(PS:我的线段树处理不是归并排序的完整再现,而是首先对根节点中的数列排序,然后判断每个点是在当前节点的左儿子区间,还是在右儿子区间,然后依次保存到儿子的数列中,因为父亲是有序的,所以儿子也是有序的。)
代码:
技术分享
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<set>
using namespace std;
#define PI acos(-1.0)
typedef long long ll;
typedef pair<int,int> P;
const int maxn=5e5+100,maxm=1e5+100,inf=0x3f3f3f3f,mod=1e9+7;
const ll INF=1e13+7;
inline int get_int()
{
    int num=0;
    char ch;
    while((ch=getchar())!= &&ch!=\n)
        num=num*10+(ch-0);
    return num;
}
/****************************/
struct edge
{
    int from,to;
    int cost;
};
edge es[maxm];
struct node
{
    int num;
    int k;
};
node sign[maxn];
int a[maxn];
int cmp(node x,node y)
{
    return x.num<y.num;
}
vector<node>tree[maxn];
void build(int l,int r,int pos)
{
    if(l==r) return;
    int mid=(l+r)/2;
    for(int i=0; i<tree[pos].size(); i++)
    {
        int k=tree[pos][i].k;
        if(l<=k&&k<=mid) tree[pos<<1].push_back(tree[pos][i]);
        else tree[pos<<1|1].push_back(tree[pos][i]);
    }
    build(l,mid,pos<<1);
    build(mid+1,r,pos<<1|1);
}
int query(int L,int R,int w,int l,int r,int pos)
{
    ///cout<<l<<" "<<r<<endl;
    if(L<=l&&r<=R)
    {
        int s=0,e=tree[pos].size()-1;
        int cou=-1;
        while(s<=e)
        {
            int md=(s+e)/2;
            if(tree[pos][md].num<=w) s=md+1,cou=md;
            else e=md-1;
        }
        ///cout<<cou+1<<endl;
        return cou+1;
    }
    int mid=(l+r)/2;
    int ans=0;
    if(L<=mid) ans+=query(L,R,w,l,mid,pos<<1);
    if(R>mid) ans+=query(L,R,w,mid+1,r,pos<<1|1);
    return ans;
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=0; i<n; i++)
    {
        scanf("%d",&a[i]);
        sign[i].num=a[i],sign[i].k=i+1;
    }
    sort(sign,sign+n,cmp);
    for(int i=0; i<n; i++) tree[1].push_back(sign[i]);
    build(1,n,1);
    while(q--)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        int L=0,R=n-1;
        int ans=-1;
        while(L<=R)
        {
            int mid=(L+R)/2;
            int w=sign[mid].num;
            ///cout<<"x="<<w<<endl;
            if((query(l,r,w,1,n,1))>=k) R=mid-1,ans=mid;
            else L=mid+1;
        }
        cout<<sign[ans].num<<endl;
    }
    return 0;
}
归并树

 

POJ 2014.K-th Number 区间第k大 (归并树)

原文:http://www.cnblogs.com/GeekZRF/p/7152755.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!