模板题 luogu3834
太菜了 蒟蒻现在才码出来主席树(还是在同桌神犇的指导下)QAQ
首先————主席树是个啥?
就是能储存历史版本的线段树
如果题目要你去创建k个连续的线段树,那么就明示了是主席树
我们先考虑最BAOLI的想法
直接建立起k个线段树
但是我们都知道线段树的玄学空间,开k个直接MLE
考虑每次加入一个新的节点后对整棵线段树的影响
其实最多一条链会被更改
我们就让原来这棵新树继承原来那颗线段树,再上面加上一条链就O~~K了
现在在想一下区间第K大怎么找
首先考虑问题简化版,如果是整个区间怎么治
很简单直接排个序找啊QAQ
如果我们以排完序的数组为叶子结点建一个线段树
查找[1,4]中的第3大
那么对于[1,1~n]区间里的第k大都可以用这个办法解决
先[1,1]建树,再用主席树一个一个从2到n加进去
那么对于不是从1开始的怎么办??
————————————差分约束————————————
用 [1,y]这棵树减[1,x-1]这棵树,再做一遍上面的处理
就出来[x,y]的第k大了
(不理解的话画个图感性认知一蛤)
数据不允许以数字大小开数组的话就离散化
code~~~~~~~~
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=4000086;
inline int Read(){// 快读
int x=0,f=1;char c=getchar();
while (!isdigit(c)){
if (c=='-')f=-1;
c=getchar();
}
while (isdigit(c)){
x=(x<<1)+(x<<3)+c-48;
c=getchar();
}
return x*f;
}
struct node{// 主席树的节点数组
int l,r,sum;//l 左子树节点 sum子树大小
}rt[maxn];
struct num{
int rank,x;
}a[maxn];// 数据数组
int sz,x,y,z;
int root[maxn];// 存主席树的节点坐标
inline void insert(int num,int &now,int l,int r){//num是插入的数的相对大小
rt[++sz]=rt[now];// now为要插入的新节点
now=sz;//先继承下的原来节点的信息
rt[now].sum++;
if (l==r)return ;
int mid=(l+r)>>1;
if (num<=mid){//如果num比中间值小 向左插入
insert(num,rt[sz].l,l,mid);
}
else {//否则向右
insert(num,rt[sz].r,mid+1,r);
}
}
inline int query(int i,int j,int k,int l,int r){// 第k大 i为被减子树当前已经到的节点编号,j为另一子树已经到达的节点编号
if (l==r)return l;
int ans=rt[rt[j].l].sum-rt[rt[i].l].sum;//查分约束 ··
int mid=(l+r)>>1;
if (k<=ans)//类似二叉搜索树
return query(rt[i].l,rt[j].l,k,l,mid);
else
return query(rt[i].r,rt[j].r,k-ans,mid+1,r);
}
inline bool cmp(num a,num b){
return a.x<b.x;
}
int n,m,rank[maxn];//rank存原数组序列中的数为第几大
int main(){
n=Read();m=Read();
for (int i=1;i<=n;i++){
a[i].x=Read();
a[i].rank=i;
}
sort(a+1,a+n+1,cmp);
for (int i=1;i<=n;i++){
rank[a[i].rank]=i;
}//离散化
for (int i=1;i<=n;i++){
insert(rank[i],root[i],1,n);//插入相对大小
}
for (int i=1;i<=m;i++){
int x,y,z;
x=Read();y=Read();z=Read();
printf("%d\n",a[query(root[x-1],root[y],z,1,n)].x);
}
return 0;
}