题目传送门:【POJ 2104】
题目大意:本题包含多组数据。每组数据都会给你一个数组,包含 n 个数;一共有 m 个询问,每次询问输入三个整数 L , R , k,表示求区间 [ L , R ] 以内第 k 小的数。( 1 ≤ n ≤ 100 000 , 1 ≤ m ≤ 5 000 , 数组中每个数的绝对值 ≤ 109 )
知识讲解:
在讲这道题之前,我想先讲讲本人对主席树的一些看法。
主席树,又被称作“可持久化线段树”,是 OI 上一个相当重要的数据结构。它可以解决例如查找历史版本的区间值,区间第 k 大/小值,以及区间内不同数的个数,等等。
既然要实现可持久化,那么在建树过程中,我们就需要将历史信息给记录下来。首先,我们最容易想到的就是:每发生一次修改过程,就建立一棵新树。但是,建立一棵新树的消耗非常大,如果要建立 m 棵新树,复杂度将为 mn∗logn ,无法承受。考虑到每次修改最多只会让这个数以及它的所有父节点发生变化,因此我们可以在修改的时候仅对这个点到根节点的数进行修改。此时建立 m 棵新树的复杂度即为 m∗logn ,节约了不少时间和空间。
那么对于每个新建的树,我们要维护它的建造时间;根据根节点的性质,每次修改会伴随着一些数的变化,而根节点总是会随着变化,所以根节点顺便也维护了建造时间。(对于区间第 k 小问题,我们只需要将根节点的建造时间为 R 的树和根节点为 L-1 的树相减即可得到这个区间的变化情况,根据它我们就可以求出这个问题的答案)
建树的时候,如果用的是指针,那么尤其需要注意的就是指针是否指向 NULL。因为建树过程中,稍微一不注意就会让指针越界,这种情况在最开始调试的时候极难被发现。所以我们在建树时,最好多加上几个判断 NULL 的条件,以免提交的时候频繁 RE 或 WA。
对于建树有疑问的请戳这里:http://www.cnblogs.com/zyf0163/p/4749042.html
需要具体分析这类问题的请戳这里:http://www.cnblogs.com/Empress/p/4652449.html
题目分析:(简略)
通过上面的分析,这道题的方向就很清楚了:一道直观的求区间第 k 小的数问题。
首先我们所有的数进行离散化,方便存储。之后对于离散化之后的原数组,将里面每一个数对应地添加至主席树的节点内。最后判断时如同上面讲的一样。
下面附上代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MX = 1000005;
struct Node{
int val;
Node *ls,*rs;
Node(){
ls = rs = NULL;val = 0;
}
};
Node node[MX*2],*tail = node,*root[MX];
int lob[MX],line[MX],ori[MX],n,m,now = 0;
Node *setup(int lf,int rt){
Node *nd = ++tail;
if (lf == rt){
nd->val = 0;
nd->ls = nd;
nd->rs = nd;
} else {
int mid = (lf + rt) / 2;
nd->ls = setup(lf,mid);
nd->rs = setup(mid + 1,rt);
nd->val = 0;
}
return nd;
}
Node *update(Node *prev,int lf,int rt,int val){
Node *nd = ++tail;
if (lf == rt){
if (prev != NULL)
nd->val = prev->val + 1;
else
nd->val++;
nd->ls = nd;
nd->rs = nd;
} else {
int mid = (lf + rt) / 2;
if (val <= mid){
nd->ls = update(prev->ls,lf,mid,val);
nd->rs = prev->rs;
nd->val = nd->ls->val + prev->rs->val;
} else {
nd->ls = prev->ls;
nd->rs = update(prev->rs,mid + 1,rt,val);
nd->val = prev->ls->val + nd->rs->val;
}
}
return nd;
}
int query(Node *left,Node *right,int lf,int rt,int val){ //一定要多加几个判 NULL
if (lf == rt)
return lf;
int mid = (lf + rt) / 2 , res;
if (left->ls != NULL && right->ls != NULL)
res = right->ls->val - left->ls->val;
else
res = right->val - left->val;
if (val <= res){
if (left->ls != NULL && right->ls != NULL)
return query(left->ls,right->ls,lf,mid,val);
return query(left,right,lf,mid,val);
}
else{
if (left->rs != NULL && right->rs != NULL)
return query(left->rs,right->rs,mid + 1,rt,val - res);
return query(left,right,mid + 1,rt,val);
}
}
int main(){
scanf("%d%d",&n,&m);
int p,q,kth;
for (int i = 1;i <= n;i++){
scanf("%d",&ori[i]);
line[i] = lob[i] = ori[i];
}
sort(lob + 1,lob + n + 1);
int size = unique(lob + 1,lob + n + 1) - (lob + 1);
root[0] = setup(1,size);
for (int i = 1;i <= n;i++){
line[i] = lower_bound(lob + 1,lob + size + 1,line[i]) - lob;
root[i] = update(root[i - 1],1,size,line[i]);
}
for (int i = 1;i <= m;i++){
scanf("%d%d%d",&p,&q,&kth);
int tmp = query(root[p - 1],root[q],1,size,kth);
printf("%d\n",lob[tmp]);
}
return 0;
}