可持久化线段树(主席树)【数组】

先搞上一波模板题:

题目描述
给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间[l, r][l,r]内的第k小值。

输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
输入输出样例

输入样例:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1

输出样例:
6405
15770
26287
25957
26287

好,那么例题给出,首先我们考虑如果是在一个区间里查询第k小元素,我们完全可以使用线段树来求解,那么好了这道题的重点来了,我们如何实现指定区间内的第k小查询;
我们可以想到如果我们需要查询【l,r】这一段区间的信息,我们可以用【1,r】的信息减去【1,l】的信息;就可以得到【l,r】的信息;
但显然,空间上是不允许我们将从【1,1】到【1,n】的每一个节点都建一棵线段树的,但我们可以发现从我们不需要每次都重建一棵线段树因为线段树节点信息很大一部分都是重复的,所以我们如果只记录发生改变的节点而把其他相同的部分全部保留,这样的空间复杂度就可以接受了;
具体看代码~
【数组版极丑代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
struct data_t{int l,r,num,lson,rson;}tre[6000005];//线段树 
struct data_p{int pos,num,to;}pot[200005];
int n,m,rel[200005],top,t;
bool cmp1(data_p a,data_p b){return a.num<b.num;}
bool cmp2(data_p a,data_p b){return a.pos<b.pos;}
void work(int a,int b)//创建一棵初始线段树 
{
    tre[a].num=1;
    if(tre[a].l==tre[a].r) {return;}
    int mid=(tre[a].l+tre[a].r)/2;
    if(b<=mid) 
    {
        tre[a].lson=++t;
        tre[t].l=tre[a].l;
        tre[t].r=mid;
        work(t,b);
    }
    else
    {
        tre[a].rson=++t;
        tre[t].l=mid+1;
        tre[t].r=tre[a].r;
        work(t,b);
    }
}
void change(int a,int b,int c)//递归建点 a是原节点,b是新节点,c是要修改的值 
{
    tre[b].num=tre[a].num+1;
    if(tre[b].l==tre[b].r)return;
    int mid=(tre[b].l+tre[b].r)/2;
    if(c<=mid)//新节点在右子树上 
    {
        tre[b].rson=tre[a].rson;
        tre[b].lson=++t;
        tre[t].l=tre[b].l;
        tre[t].r=mid;
        change(tre[a].lson,t,c);
    }
    else//新节点在左子树上 
    {
        tre[b].lson=tre[a].lson;
        tre[b].rson=++t;
        tre[t].r=tre[b].r;
        tre[t].l=mid+1;
        change(tre[a].rson,t,c);
    }


}
int find(int a,int b,int c)//查询 
{
    if(tre[a].l==tre[a].r) return tre[a].l;
    int x=tre[tre[a].lson].num-tre[tre[b].lson].num;
    if(c<=x) return find(tre[a].lson,tre[b].lson,c);
    else return find(tre[a].rson,tre[b].rson,c-x);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {scanf("%d",&pot[i].num);pot[i].pos=i;}
    //离散化
    sort(pot+1,pot+n+1,cmp1);
    for(int i=1;i<=n;i++)
    {
        rel[++top]=pot[i].num;pot[i].to=top;
        while(pot[i].num==pot[i+1].num)pot[++i].to=top;
    }
    sort(pot+1,pot+n+1,cmp2);
    for(int i=1;i<=n;i++)//建树 
    {
        tre[i].l=1;tre[i].r=top;
        tre[i].num=i;
    }t=n;
    work(1,pot[1].to);
    for(int i=2;i<=n;i++) 
    {
        change(i-1,i,pot[i].to);
    }
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        printf("%d\n",rel[find(y,x-1,z)]);
    }


    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值