主席树概述

先不瞎说什么,直接上模板题:P3834 【模板】可持久化线段树 1(主席树)

题目背景

这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

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

输入格式:

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

输出格式:

输出包含k行,每行1个正整数,依次表示每一次查询的结果




这道题简单来说就是求区间第k大数(好像可以树套树)。不过我们这里有一个主席树做法,并不知道为什么叫主席树,其实就是动态开点线段树套一个可持久化。也不是什么新的数据结构,只是需要对可持久化有一个理解。

显然需要开一个权值线段树,记录每个下标的个数。然后我们可持久化:每插入一个数据,我们可持久化一次,于是我们查询每一个前缀的信息。

然后我们想一想平常查询第k大是怎么做的?如果左儿子个数大于 k k ,就找左儿子里的第k个,如果左儿子个数小于 k k ,就找右儿子里第(k-左儿子个数)个。

然后最重要的一步就来了,主席树可持久化的信息是可减的!!!比如前 n n [l,r]间有 ni n i 个数,前 m m [l,r]间有 mi m i 个数,那么可得在 [n+1,m] [ n + 1 , m ] [l,r] [ l , r ] 间有 mini m i − n i 个数。不是很神奇么!!!

那主席树不变成水题么?我们查询的时候传两个参,就是两个时间点的 root r o o t ,然后减一减,判一判就好了。

来波代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
using namespace std;  
struct Tree{  
    int num,lson,rson,l,r;  
}b[20000007];  
int n,m,ml=0x7f7f7f7f,mr=-0x7f7f7f7f,cnt;  
int data[200005],root[200005];//开始wa了两个点,才发现开的1e5...  

void insert(int &u,int l,int r,int x)//插入操作  
{  
    int y=u;  
    u=++cnt;  
    b[u]=b[y];//新建节点,复制一波  
    if(y==0) b[u].l=l,b[u].r=r;//如果本来是0,就要赋一波管理区间  
    b[u].num++;  
    if(l==r) return;  
    int mid=(l+r)/2;  
    if(x>mid) insert(b[u].rson,mid+1,r,x);  
    else insert(b[u].lson,l,mid,x);  
}  

int ques(int x,int y,int k)//查询  
{  
    if(b[y].l==b[y].r) return b[y].l;//一定要是y!!!十分重要,开始写x输出全是0  
    if(k>(b[b[y].lson].num-b[b[x].lson].num))//减一减就表示这一段区间的个数,其实就是把以前的b[b[u].lson].num变成了(b[b[y].lson].num-b[b[x].lson].num)  
      return ques(b[x].rson,b[y].rson,k-b[b[y].lson].num+b[b[x].lson].num);  
    else  
      return ques(b[x].lson,b[y].lson,k);  
}  

void check(int u)//调试函数  
{  
    if(u==0) return;  
    cout<<b[u].l<<" "<<b[u].r<<" "<<b[u].num<<endl;  
    check(b[u].lson);  
    check(b[u].rson);  
}  

int main()  
{  
    scanf("%d%d",&n,&m);  
    for(int i=1;i<=n;i++)  
      scanf("%d",&data[i]),ml=min(ml,data[i]),mr=max(mr,data[i]);  
    for(int i=1;i<=n;i++)  
      root[i]=root[i-1],insert(root[i],ml,mr,data[i]);  
    //check(root[2]);  
    for(int i=1;i<=m;i++)  
    {  
        int x,y,z;  
        scanf("%d%d%d",&x,&y,&z);  
        printf("%d\n",ques(root[x-1],root[y],z));  
    }  
}   
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值