先不瞎说什么,直接上模板题:P3834 【模板】可持久化线段树 1(主席树)
题目背景
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入格式:
第一行包含两个正整数
N、M
N
、
M
,分别表示序列的长度和查询的个数。
第二行包含
N
N
个正整数,表示这个序列各项的数字。
接下来行每行包含三个整数
l,r,k
l
,
r
,
k
表示查询区间
[l,r][l,r]
[
l
,
r
]
[
l
,
r
]
内的第
k
k
小值。
输出格式:
输出包含行,每行1个正整数,依次表示每一次查询的结果
这道题简单来说就是求区间第k大数(好像可以树套树)。不过我们这里有一个主席树做法,并不知道为什么叫主席树,其实就是动态开点线段树套一个可持久化。也不是什么新的数据结构,只是需要对可持久化有一个理解。
显然需要开一个权值线段树,记录每个下标的个数。然后我们可持久化:每插入一个数据,我们可持久化一次,于是我们查询每一个前缀的信息。
然后我们想一想平常查询第k大是怎么做的?如果左儿子个数大于 k k ,就找左儿子里的第个,如果左儿子个数小于 k k ,就找右儿子里第(-左儿子个数)个。
然后最重要的一步就来了,主席树可持久化的信息是可减的!!!比如前 n n 个间有 ni n i 个数,前 m m 个间有 mi m i 个数,那么可得在 [n+1,m] [ n + 1 , m ] 中 [l,r] [ l , r ] 间有 mi−ni 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));
}
}