POJ K-th Number

[文章目录]

Description

给出含n个数的序列,给出m次询问,每次询问区间[l,r]中第k大的数是什么。n<=100000,m<=50000

主席树求区间第k大值。
主席树,是一种维护前缀区间中各种值出现次数的权值线段树。

考虑先将数离散化。然后对于每个前缀维护每个数出现的次数。发现[1,i]区间的线段树与[1,i+1]区间的线段树只在点hash_w[i]不同,大部分节点不变,于是为了节省空间,直接将这个seg与前面节点相同的位置放上前面的节点。

根据每个节点到根的路径长度可知,开了至多logn个新节点。所以空间复杂度(但好像开的太满会RE)。

对于每个询问,调用1~r,1~l-1的权值线段树,每个节点[a,b]进行作差,差的值即为数a~b中在区间[l,r]出现的次数。那么一直递归求解直到到达叶子节点。
预处理时间复杂度O(),询问复杂度O()。

代码难度还可以吧。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
using namespace std;
int n,m,root[N],w[N],b[N],c[N],tot,cnt;
struct seg
{
    int l,r,sum;
}tre[N*20];
int hash(int x)
{
    int mid,l=1,r=tot+1;
    while(l<r)
    {
        mid=l+r>>1;
        if(c[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r;
}
void updata(int l,int r,int &x,int y,int z)
{
    tre[++cnt]=tre[y]; tre[cnt].sum++; x=cnt;
    if(l==r) return ;
    int mid=l+r>>1;
    if(z<=mid) updata(l,mid,tre[x].l,tre[y].l,z);
    else updata(mid+1,r,tre[x].r,tre[y].r,z);
}
int query(int l,int r,int x,int y,int z)
{
    if(l==r) return r;
    int mid=l+r>>1,tmp=tre[tre[y].l].sum-tre[tre[x].l].sum;
    if(tmp>=z) query(l,mid,tre[x].l,tre[y].l,z);
    else query(mid+1,r,tre[x].r,tre[y].r,z-tmp);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {scanf("%d",w+i);b[i]=w[i];}
    sort(b+1,b+n+1); c[++tot]=b[1];
    for(int i=2;i<=n;i++) if(b[i]!=b[i-1]) c[++tot]=b[i];
    for(int i=1;i<=n;i++) updata(1,tot,root[i],root[i-1],hash(w[i]));
    int l,r,k;
    while(m--)
    {
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",c[query(1,tot,root[l-1],root[r],k)]);
    }
    return 0;
}

发表评论

邮箱地址不会被公开。 必填项已用*标注