poj 2104 k-th number

K-th Number
Time Limit: 20000MS Memory Limit: 65536K
Total Submissions: 45330 Accepted: 15083
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.

Source

Northeastern Europe 2004, Northern Subregion


题解:这是一道主席树静态区间查询第K大的模板题

研究了一下午的主席树,总算懂得了一点眉目。

什么是主席树

可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它

们之间的共同数据来减少时间和空间消耗。

因此可持久化线段树也叫函数式线段树又叫主席树。

主席树的主体是线段树,跟准确的说是由多颗形态相同的线段树所构成的。所谓主席树就是给每个区间[1,i]都建立一颗线段树,但如果每个区间都重新构建线段树的话不仅在时间是无法承受,空间也大的惊人。于是就发现了主席树一个很好的性质,那就是每新建的一个区间都是依附前一个区间存在的,什么意思呢?就是要充分他的前一颗树。用两个指针,分指当前空树和前一棵树。因为每棵树的结构是一样的,只是某些节点控制的数目不同,但是两棵相邻的

树,只有一数只差,因此,如果元素要进左子树的话,右子树就会跟上个树这个区间的右子树是完全一样的,因此,

可以直接将本树本节点的右子树指针接到上棵树当前节点的右儿子,并且对于每次更改,最多增加logn 个节点,所以空间复杂度约为nlogn ,这样即省时间,又省空间。

具体情况见下图:


在建立线段树时一般都需要记录三个值,左右子树根节点的编号以及这颗线段树中一共有多少个数。

在建树时必须要保证这颗线段树的先序遍历是升序排列的,也就是说当前这个数在原数列中是第几大,那么他在线段

树[1,n]的区间中的位置就在哪,即为下标。那有人肯定会问,如果数值很大的话,该怎么办呢?这里引入一种思想

——离散化。

何为离散化?

就是给一个数列按从小到大的顺序编号,赋予他一个新的值。

举个例子:

原数列  12 15 87 69 42

离散化后 1 2 5 4 3

有了这些铺垫,就可以用主席树求解区间第K小了

因为每棵树的形态相同,那么就可以做减法。跟一般的,在整棵树中找第k个数是一样的。如果一个节点的左权值(左

子树上点的数量之和)大于k,那么就到左子树查找,否则到右子树查找。其实主席树也一样的。对于任意两棵树(分

别存区间[1,i]和区间[1,j] i<j),在同一节点上(两节点所表示的区间相同),控制节点的个数之差表示的是,原序列

区间[i,j]当前节点所表示的区间里,有多少数是在这个区间里的。同理,对于同一节点,如果在两棵树中,它们的点

之差大于等于k,那么要求的数就在左孩子,否则在右孩子。当递归到叶子节点时,就可以输出了。具体的过程可以

结合代码,感性的思考一下(其实有点类似于平衡树找第K大的感觉)。


#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#define N 100003  
#define M 400003  
using namespace std;  
int a[N],b[N],p[N],root[N],n,m,sz;  
struct data  
{  
  int l,r,w;  
};data tree[M*20];  
int cmp(int x,int y)  
{  
  return a[x]<a[y];  
}  
void insert(int &i,int l,int r,int x)  
{  
    tree[++sz]=tree[i]; i=sz;  
    tree[i].w++;  
    if (l==r) return;  
    int mid=(l+r)>>1;  
    if (x<=mid) insert(tree[i].l,l,mid,x);  
    else insert(tree[i].r,mid+1,r,x);  
}  
int query(int i,int j,int l,int r,int k)  
{  
    if (l==r) return l;  
    int t=tree[tree[j].l].w-tree[tree[i].l].w;  
    int mid=(l+r)>>1;  
    if (t>=k) return query(tree[i].l,tree[j].l,l,mid,k);  
    else return query(tree[i].r,tree[j].r,mid+1,r,k-t);  
}  
int main()  
{  
  scanf("%d%d",&n,&m);  
  for (int i=1;i<=n;i++)  
   {  
   scanf("%d",&a[i]);  
   p[i]=i;  
   }  
  sort(p+1,p+n+1,cmp);  
  for (int i=1;i<=n;i++)  
   b[p[i]]=i;//离散化,p[i]表示第i小的值在a[]中的下标   
  sz=0; root[0]=0;  
  for (int i=1;i<=n;i++)  
   {  
     root[i]=root[i-1];  
     insert(root[i],1,n,b[i]);  
   }  
  for (int i=1;i<=m;i++)  
   {  
     int x,y,z;  
     scanf("%d%d%d",&x,&y,&z);  
     int t=query(root[x-1],root[y],1,n,z);  
     printf("%d\n",a[p[t]]);  
   }  
}  




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值