P3834 【模板】可持久化线段树 2(主席树)

题目链接

在这里插入图片描述
在这里插入图片描述

静态查询区间第k小问题。

我们先要知道,权值线段树可以维护整体区间的桶,查询整体区间第k小。

虽然这题是查询子区间[l,r]的第k小,但我们可以转化成整体区间第k小问题:

我们对每个前缀都建一棵权值线段树,用于维护出现个数。

则[1,r]区间对应的线段树减去[1,l-1]对应的线段树后,得到的那棵线段树就是以[l,r]为整体区间的权值线段树。

那么[l,r]区间内的第k小值就等于 :[1,r]区间对应的线段树减去[1,l-1]对应的线段树后,得到的那棵线段树的整体区间第k小。(这里是线段树合并的思想,可以证明这两颗线段树是具有可加性的)
(两棵线段树相减指对应的节点的值相减)
在这里插入图片描述

但是如果老老实实对每个前缀都建树的话一共有2e5棵树,肯定不行。
而且我们注意到第i棵线段树和第i-1棵线段树相比其实就是单点修改,所以这时候主席树就派上用场了。

我们先建一棵空树,版本为0,
然后依次插入n个点,第i次插入得到的线段树版本为i 。

建树方法有两种,一种是把数据离散化后建树,
一种是动态开点建可持久化权值线段树。(名字瞎编的,大概就是这两种线段树的交集?)

总结一下,这题本质上就是利用了前缀建树的线段树可加性建主席树,把每次询问的[l,r]区间转化成了一棵权值线段树的根节点区间,于是问题就成了在相减得到的那颗权值线段树里面,求整体区间第k小问题。

实现过程就看代码吧。

//离散化建树
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5+7;
const int mod = 1e9+7;
const ll inf = 34359738370;
int n,m;
int a[maxn],h[maxn],size;//h为离散化后的数组 size为离散化去重后的个数 也即是线段树建树区间长度[1,size]
int tree[maxn<<5],lc[maxn<<5],rc[maxn<<5],cnt=0;
//对离散化后的数组建树后, [l,r]表示离散化数组h[l....r]中每个数在原先数组出现次数之和
int root[maxn];//每个版本的根序号 第i棵树就是对第i个前缀建的树  根序号为root[i]

//给定序列  q次查询  [l,r]区间的第k小
//对每个前缀序列建线段树 [l,r]区间第k小就相当于[1,r]对应的线段树减去[1,l-1]对应的线段树之后得到的线段树中的第k小
//线段树1-线段树2 指 对应每个节点的值相减
void build(int &rt,int l,int r)
{
    rt=++cnt;
    if(l == r)
    {
        return ;
    }
    int mid=(l+r)>>1;
    build(lc[rt],l,mid);
    build(rc[rt],mid+1,r);
}
void updata(int &rt,int last,int l,int r,int pos)//last线段树版本上的单点修改
{
    rt=++cnt;
    tree[rt]=tree[last]+1;//每次给桶加入一个数 也就是rt版本要修改的区间比last版本多1个数
    lc[rt]=lc[last],rc[rt]=rc[last];
    if(l == r) return ;//叶子节点已经+1了
    int mid=(l+r)>>1;
    if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos);
    else updata(rc[rt],rc[last],mid+1,r,pos);
}
int kth(int ltree,int rtree,int l,int r,int k)//ltree对应区间是[1,l-1] rtree对应区间是[1,r]  
  //查询的本质就是在两棵线段树相减得到的线段树上查询整体区间第k小
{
    if(l == r) return l;//当前线段树[l,l]区间对应在离散化数组的数就是第k小数
    int num=tree[lc[rtree]]-tree[lc[ltree]];
    //num表示 两树相减得到的线段树的左子树区间的值 也就是桶里面数的个数 (和权值线段树查kth是一样的思路)
    int mid=(l+r)>>1;
    if(k<=num) return kth(lc[ltree],lc[rtree],l,mid,k);
    else return kth(rc[ltree],rc[rtree],mid+1,r,k-num);
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        h[i]=a[i];
    }
    //离散化步骤 先排序后去重
    sort(h+1,h+n+1);
    size=unique(h+1,h+n+1)-h-1;//返回的是去重后末尾的下一位 所以要-1
    build(root[0],1,size);//第0版本的线段树是空树
    for(int i=1;i<=n;i++)
    {
        int index=lower_bound(h+1,h+size+1,a[i])-h;
        updata(root[i],root[i-1],1,size,index);//h[index]这个值在桶中出现的次数+1
    }
    while(m--)
    {
        int x,y,k;
        scanf("%d %d %d",&x,&y,&k);
        printf("%d\n",h[kth(root[x-1] ,root[y] ,1,size,k)]);
    }
    return 0;
}
//动态开点可持久化权值线段树
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5+7;
const ll inf = 34359738370;
const int INF = 1e9+5;
int a[maxn];
int tree[maxn*105],lc[maxn*105],rc[maxn*105],cnt=0,root[maxn];
int n,m;
inline void updata(int &rt,int last,int l,int r,int pos)
{
    rt=++cnt;
    tree[rt]=tree[last]+1;
    lc[rt]=lc[last],rc[rt]=rc[last];
    if(l == r) return;
    int mid=(l+r)>>1;
    if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos);
    else updata(rc[rt],rc[last],mid+1,r,pos);
}
int query(int rt1,int rt2,int l,int r,int k)
{
    if(l == r) return l;
    int num=tree[lc[rt2]]-tree[lc[rt1]];
    int mid=(l+r)>>1;
    if(k<=num) return query(lc[rt1],lc[rt2],l,mid,k);
    else return query(rc[rt1],rc[rt2],mid+1,r,k-num);
}
int main()
{
    scanf("%d %d",&n,&m);
    //动态开点 不需要建树  直接插入就好 而这里插入过程也是线段树合并过程
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        updata(root[i],root[i-1],-INF,INF,a[i]);
    }
    while(m--)
    {
        int x,y,k;
        scanf("%d %d %d",&x,&y,&k);
        printf("%d\n",query(root[x-1],root[y],-INF,INF,k));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值