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

K-th Number

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;
}

归并树

时间: 2024-10-25 12:10:54

POJ 2014.K-th Number 区间第k大 (归并树)的相关文章

POJ 2104 K-th Number(区间第k大数)(平方分割,归并树,划分树)

题目链接: http://poj.org/problem?id=2104 解题思路: 因为查询的个数m很大,朴素的求法无法在规定时间内求解.因此应该选用合理的方式维护数据来做到高效地查询. 如果x是第k个数,那么一定有 (1)在区间中不超过x的数不少于k个 (2)在区间中小于x的数有不到k个 因此,如果可以快速求出区间里不超过x的数的个数,就可以通过对x进行二分搜索来求出第k个数是多少. 接下来,我们来看一下如何计算在某个区间里不超过x个数的个数.如果不进行预处理,那么就只能遍历一遍所有元素.

POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)

题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设x是第k个数,那么一定有 (1)在区间中不超过x的数不少于k个 (2)在区间中小于x的数有不到k个 因此.假设能够高速求出区间里不超过x的数的个数.就能够通过对x进行二分搜索来求出第k个数是多少. 接下来,我们来看一下怎样计算在某个区间里不超过x个数的个数. 假设不进行预处理,那么就仅仅能遍历一遍全

【POJ】【2104】区间第K大

可持久化线段树 可持久化线段树是一种神奇的数据结构,它跟我们原来常用的线段树不同,它每次更新是不更改原来数据的,而是新开节点,维护它的历史版本,实现“可持久化”.(当然视情况也会有需要修改的时候) 可持久化线段树的应用有很多,仅以区间第K大这种简单的问题来介绍这种数据结构. 我们原本建立的线段树是表示区间的,或者说,维护的是[位置],存的是每个位置上的各种信息.它的优点是满足区间加法,但不满足区间减法,所以我们这里要换一种建树方式:对于每个区间[1,i]建立一棵权值线段树.这个线段树的作用其实就

HDU 2665.Kth number 区间第K小

Kth number Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 11394    Accepted Submission(s): 3465 Problem Description Give you a sequence and ask you the kth big number of a inteval. Input The f

poj 2104主席树求区间第k小

POJ - 2104 题意:求区间第k小 思路:无修改主席树 AC代码: #include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set&

POJ 2104 K-th Number 主席树 区间第K大

今天第一次接触可持久化数据结构,还是有必要总结一下的. 首先对于查找第k大的问题,先搞清楚怎么样通过利用N颗线段树来求解.如果是求全局第K大,那么可以把数字的值作为位置插入线段树,然后通过区间和+二分来找到第k个位置.因为是通过区间和来找第k大的,显然是满足前缀和性质的,所以查询l,r区间的第k打,就直接根据1-l - 1,1-r两个区间建立两颗线段树,然后通过节点依次相减来求得第k大值.所以这样子解需要的内存空间是n*n*logn的(不需要管数的范围,范围再大也可以通过离散化缩小到n). 但是

Poj 2104区间第k大(归并树)

题目链接 K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 36890 Accepted: 11860 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key ins

poj 2401 划分树 求区间第k大的数

题目:http://poj.org/problem?id=2104 划分树待我好好理解下再写个教程吧,觉得网上的内容一般,,, 模板题: 贴代码: #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a) memset(a,0,sizeof(a)) const int MAXN = 1000

POJ 2104 区间第K大值(划分树做法)

由于深感自己水平低下,把大部分有效时间放在了刷题上,于是好久没写题解了.今天刚学了下划分树的原理,于是写道简单题练练手. 题目链接:http://poj.org/problem?id=2104 划分树的空间复杂度和时间复杂度均为O(nlogn),对于解决该问题而言,每次查询的复杂度为O(logn),比归并树O((log)^3)节省时间[归并树采用了三次二分]. 但是划分树也有自己的缺点,不支持更新以及适用范围狭窄,总之...是时代的眼泪了= = AC代码: #include <iostream>