题目大意:给出一段数列,让你求[L,R]区间内第几大的数字!
在这里先介绍一下主席树!
如果想了解什么是主席树,就先要知道线段树,主席树就是n棵线段树,因为线段树只能维护最大值或者最小值,要想求出第二大的数字怎么办呢?两颗线段树呗!好,那么第n大呢,就可以构造n棵线段树,这样的内存是显然会爆掉的,那么怎么办呢?因为每一次更新都是更新的是从叶子节点到根节点的一条路,路的长度大约是lognlogn,如下图红色的为更新的时候变化的节点:
当进行更新操作的时候,也就是新建一个线段树,但是这可线段树不会全部都建立起来,只把修改的节点建立一下就可以,如果节点不修改就可以用以前线段树的节点指向当前新建的线段树的节点,先看下图然后在解释:
对于没有修改的节点就直接指向新建的线段树就可以了!
对于每一棵线段树都是类似于下图的样子的:
这棵树表示数字1出现了三次,数字2出现了1次,数字三出现了一次,数字五出现了4次,数字6出现了三次;
用一个数组来表示就是sum[i]=j
表示数字i出现了j次。
这样在通过一般线段树的pushdown
操作就变为了我们熟悉的线段树了!如下图:
下面据一个例子[1,1,2,1,5,5,5,5,6,6,6,3]解释一下建立线段树的全部过程:
首先是一棵空的线段树
顺序的由第一个元素开始进入线段树并且开始更新:
这样我们就可以知道不同状态下的线段树了,比如sum[i]当前节点对应的数字出现了几次,对于[L,R][L,R]这个区间我们就可以用sum[R]−sum[L−1]sum[R]−sum[L−1]表示出如果这个区间大于k那么就从他的左子树接着找,否则从右子树中找K−(sum[R]−sum[L−1])K−(sum[R]−sum[L−1])这个多个就是第k大的数字!
这个基于的原理举个例子就知道了我们知道区间[1,3,3,4,5]区间一共有5个元素那么第5大的就是最后一个元素了!
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct Node{
int a,b,rs,ls,sum;
}tr[2000010];
int a[100010],b[100010];
int rt[100010],pos,cnt;
void Build(int &node,int a,int b)
{
node=++cnt;
tr[node].a=a;
tr[node].b=b;
if(a==b)return;
int mid=(a+b)>>1;
Build(tr[node].ls,a,mid);
Build(tr[node].rs,mid+1,b);
}
void Insert(int pre,int &node)
{
node=++cnt;
tr[node].ls=tr[pre].ls;
tr[node].rs=tr[pre].rs;
tr[node].a=tr[pre].a;
tr[node].b=tr[pre].b;
tr[node].sum=tr[pre].sum+1;
if(tr[node].a==tr[node].b)return;
int mid=(tr[node].a+tr[node].b)>>1;
if(mid>=pos)Insert(tr[pre].ls,tr[node].ls);
else Insert(tr[pre].rs,tr[node].rs);
}
int Query(int pre,int node,int k)
{
if(tr[node].ls==tr[node].rs)return b[tr[node].a];
int cmp=tr[tr[node].ls].sum-tr[tr[pre].ls].sum;
if(cmp>=k)return Query(tr[pre].ls,tr[node].ls,k);
else return Query(tr[pre].rs,tr[node].rs,k-cmp);
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;b[i]=a[i],i++)
scanf("%d",&a[i]);
sort(b+1,b+n+1);
Build(rt[0],1,n);
for(int i=1;i<=n;i++)
{
pos=lower_bound(b+1,b+n+1,a[i])-b;
Insert(rt[i-1],rt[i]);
}
int l,r,k;
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",Query(rt[l-1],rt[r],k));
}
return 0;
}