区间第k大值(主席树入门)

K-th Number POJ - 2104

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

不管是求区间第k大还是区间第k小,会求一个就另一个。

通常我们求全数组的第k大会先将其排序,然后第k大就为a[k],但是现在不是全部区间,而是部分区间,要是通过排序再找第k大数的话就显得太暴力了。

所以这时我们需要用到一种新的数据结构——主席树,咳咳,别被这个主席吓着了,其实就是“巧用线段树”而已。

我们要求区间l到r的第k大就是要在这个区间从最大数往最小数数k个!明白了这个,拷贝一份number[],先排序,再去重,然后我们可以以number[]为基底建n个线段树,第i个线段树保存a[]前i个数,这样当我们查询区间l->r的第k大值的时候,只需在第l-1-和第r颗树中从大到小数不同的个数即可,因为第r颗树相比于第l-1颗树多了a[l->r]的数。

这时你可能会有个问题(没有假设你有):一颗线段树就占4n了,n颗不会爆内存?况且线段树拷贝还会爆时间!?

这就是主席树的巧妙之处了——虚节点!注意到,我们每加一个数进来其实相比于前面的一个线段树,这颗线段树只更新了从根节点到一个叶子节点路径上值,所以这颗线段树的其他节点就与上一颗树共用一个就行了,这样每次更新只会多增加树的深度个节点(logn个),避免了内存浪费和拷贝的时间浪费。

下面是本题求第k小的代码,看懂了了的话,试着将它改成求第k大的(只改query()函数,不要变相的用求第k小的去求k大,追求实际理解),还可以试着简化一下代码——将maketree()函数去掉,想想为什么。

Good Luck!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

struct Node
{
    int l,r,sum;
}tree[3000005];
int a[100005],number[100005],root[100005],node_num;

void maketree(int l,int r,int &node)
{
    node=++node_num;
    if(l==r)return;
    int mid=(l+r)>>1;
    maketree(l,mid,tree[node].l);
    maketree(mid+1,r,tree[node].r);
}

void addtree(int l,int r,int &node,int pre,int pos)
{
    node=++node_num;
    tree[node]=tree[pre];
    tree[node].sum++;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pos<=mid)addtree(l,mid,tree[node].l,tree[pre].l,pos);
    else addtree(mid+1,r,tree[node].r,tree[pre].r,pos);
}

int query(int l,int r,int node,int pre,int k)
{
    if(l==r)return l;
    int chal=tree[tree[node].l].sum-tree[tree[pre].l].sum;
    int mid=(l+r)>>1;
    if(k<=chal)return query(l,mid,tree[node].l,tree[pre].l,k);
    else return query(mid+1,r,tree[node].r,tree[pre].r,k-chal);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        number[i]=a[i];
    }
    sort(number+1,number+1+n);
    int num=unique(number+1,number+1+n)-number-1;

    node_num=0;
    maketree(1,num,root[0]);
    for(int i=1;i<=n;i++){
        int pos=lower_bound(number+1,number+1+num,a[i])-number;
        addtree(1,num,root[i],root[i-1],pos);
    }

    int l,r,k;
    while(m--){
        scanf("%d%d%d",&l,&r,&k);
        int pos=query(1,num,root[r],root[l-1],k);
        printf("%d\n",number[pos]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值