主席树 —— ②静态区间第K小

本文介绍如何使用主席树解决区间第K小问题,通过构建权值线段树统计区间内元素出现次数,实现快速查找。利用离散化、二分思想及线段树节点值,高效获取[L,R]区间内第K小的数。

静态区间第K小

给出一段长度序列 a[1]、a[2]、… 、a[N],每次询问 [L, R] 区间内第K小的数为?

我们可以建立 权值线段树,线段树内存储 某个权值的出现次数

对于 [L, R] 区间(a[L] ~ a[R]),构建的线段树

以下示例参考blog:https://www.cnblogs.com/LiuRunky/p/Sustainable_Segment_Tree.html

例如:数列a={10,20,30,20,50,10,60,40},离散化后得到b={1,2,3,2,5,1,6,4}
对于数列内每一个离散化后的数,我们建立一个基于数值的 区间和线段树 统计它的出现次数
在这里插入图片描述
(7、8是用来占位的,可以无视)

这样,我们可以通过类似二分的思想找到第k小,而线段树的节点已经帮助我们将区间对半切分

假设我们想找区间第7小:

step 1: 区间[1,4]内的数一共出现了6次,所以我们可以直接进入另一区间[5,8],并且找这个区间中的第1小

step 2: 区间[5,6]内的数一共出现了2次,所以[5,8]中的第1小一定也是[5,6]中的第1小

step 3: 区间[5,5]内的数一共出现了1次,所以5正是[5,6]中的第1小,即整个查询区间中的第7小

那要怎么得到区间[L, R]的线段树呢,这里就要用到主席树,先建个出现次数全为0的空树,然后依次更新加入 a[1]、a[2]、… 、a[N],那我们就得到了N个版本的权值线段树(可以看作一个 树形的前缀和

对于任意一个结点,只需要用 第R个版本第L-1个版本 的线段树同位置结点相减,从L-1 到R这个结点增加的值,既是 [L, R]的线段树的值



模板题:POJ2104 - K-th Number

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+50;
int n,m,N,a[maxn],b[maxn],root[maxn];
int t[maxn<<5],ls[maxn<<5],rs[maxn<<5],cnt=0;
int build(int l,int r)
{
    int rt=++cnt;
    if(l==r)
    {
        t[rt]=0;
        return rt;
    }
    int mid=(l+r)>>1;
    ls[rt]=build(l,mid);
    rs[rt]=build(mid+1,r);
    t[rt]=t[ls[rt]]+t[rs[rt]];
    return rt;
}
int updata(int l,int r,int pos,int pre)
{
    int rt=++cnt;
    if(l==r)
    {   //出现次数+1
        t[rt]=t[pre]+1;   //注意,不是t[rt]++
        return rt;
    }
    ls[rt]=ls[pre],rs[rt]=rs[pre];
    int mid=(l+r)>>1;
    if(pos<=mid)
        ls[rt]=updata(l,mid,pos,ls[pre]);
    else
        rs[rt]=updata(mid+1,r,pos,rs[pre]);
    t[rt]=t[ls[rt]]+t[rs[rt]];
    return rt;
}
int query(int l,int r,int rt1,int rt2,int K)
{
    if(l==r)
        return l;  //返回对应的权值
    int suml=t[ls[rt2]]-t[ls[rt1]];    //左子树出现次数之和
    int mid=(l+r)>>1;
    if(suml>=K)
        return query(l,mid,ls[rt1],ls[rt2],K);   //L-1树和R树的rt要一同更新
    else
        return query(mid+1,r,rs[rt1],rs[rt2],K-suml);  //注意变为了K-suml
}
void init()
{
    for(int i=1;i<=n;i++)
        b[i]=a[i];
    sort(b+1,b+n+1);
    N=unique(b+1,b+n+1)-(b+1);   //离散化以后不同数的个数
    for(int i=1;i<=n;i++)        //即离散化后变为1 ~ N
        a[i]=lower_bound(b+1,b+N+1,a[i])-b;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    init();    //离散化
    root[0]=build(1,N);   //先建个空树
    for(int i=1;i<=n;i++)
        root[i]=updata(1,N,a[i],root[i-1]);  //依次更新加入
    for(int i=1;i<=m;i++)
    {
        int L,R,K;
        scanf("%d %d %d",&L,&R,&K);
        printf("%d\n",b[query(1,N,root[L-1],root[R],K)]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值