http://poj.org/problem?id=2104
题意:给定N个数的数组,求Q次询问的区间第K小的数。
思路:用划分树求解。所谓的划分树,说白了就是线段树的一种扩展,在划分树中,树的每个结点还是表示一个区间,假设是(l,r),只不过和线段树不同的是划分树对每个结点还维护一个数组,数组的长度为区间的长度(即:(r-l+1))。然后我们就是对区间进行划分,划分的依据就是找到一种“基准元”(因为是划分嘛,肯定是需要一个比较元素的),比这个元素小的元素被放入该结点的左子树中,比这个元素大的放入右孩子中,和这个元素一样大的根据一定的“要求放入”左右子树(这个规则请见代码)。接着我们要解决的问题就是这个“基准元素”怎么找的问题,这个基准元素要满足的条件是:该基准元素是(l,r)区间的中间元素,也就是说有一半的元素比基准元素小,一半大,这个地方也是这个算法最难理解的地方(个人认为这个地方比较难),我们首先将原来数组的元素进行排序,得到s[]数组,在每次进行区间划分之前求中值的时候,我们把s[mid]作为(l,r)的中值(mid = (l + r) >> 1),然后对区间进行上述所描述的划分,比s[mid]小的入左子树,大的如右子树。具体请见下图:
其中的sorted[ ]数组就是对原数组排序之后的数组,val[0][ ]数组是原来的初始数组,va[1][ ]是对原数组进行一次划分之后的结果,接下去依次类推,最终达到叶子结点。
划分树有两种操作:建树、查询。
1、建树:
/*
tol[deep][i] :表示树的第deep层中,i到i所在区间的开始处这
个区间内有多少个元素入了左子树, 比较别扭啊。。
val[deep][i]:第deep层中的i号下标的数组元素。
*/
void Build(int idx, int l ,int r, int deep){
p[idx].l = l ; p[idx].r = r ;
if(l == r) return ;
int mid = MID(l, r) ;
int v= s[mid] ; //基准元素
int lnum = mid - l + 1 ; //(l,mid)中有多少个和基准元素相等的元素,即有多少个和基准元素相等元素可以入左子树
for(int i=l;i<=r;i++){ //这个地方的循环是从(l,r), 请注意
if( val[deep][i]<v ) lnum -- ;
}
int lto , rto ;
lto = rto = 0;
for(int i=l;i<=r;i++){
if(i == l) tol[deep][i] = 0 ;
else tol[deep][i] = tol[deep][i-1] ;
if(val[deep][i] < v){ //进入左子树
tol[deep][i] ++ ;
val[deep+1][ l+lto++ ] = val[deep][i] ;
}
else if(val[deep][i] > v){ //进入右子树
val[deep+1][ mid+1+rto++ ] = val[deep][i] ;
}
else if(val[deep][i] == v){ //相等时,需要判断是进入左子树还是右子树
if(lnum > 0){ //进入左子树
tol[deep][i] ++ ;
val[deep+1][ l+lto++ ] = val[deep][i] ;
lnum -- ;
}
else{ //进入右子树
val[deep+1][ mid+1+rto++ ] = val[deep][i] ;
}
}
}
Build( L(idx),l,mid ,deep+1) ;
Build( R(idx) ,mid+1,r,deep+1) ;
}
2、查询:
int query(int l,int r , int k , int idx,int deep){
if(l == r) return val[deep][l] ;
int sum ,ss;
if(l == p[idx].l) ss = 0 ;
else ss = tol[deep][l-1] ;
sum = tol[deep][r] - ss ;
int s = p[idx].l ;
int t = p[idx].r ;
int mid = MID(p[idx].l , p[idx].r) ;
if(sum >= k){
return query(s+ss, s+tol[deep][r]-1 , k, L(idx) , deep+1);
}
else{
int ee = l - s + 1 - ss ;
int e = r - l - sum ;
return query(mid+ee , mid+ee+e, k-sum , R(idx) ,deep+1);
}
}
本题的完整代码:
/*
划分树
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 100010
#define L(r) (r<<1)
#define R(r) ((r<<1)+1)
#define MID(l,r) ((l+r)>>1)
int N, M ;
int d[MAXN] ,s[MAXN];
int val[30][MAXN] ;
struct Node{
int l ,r ;
}p[MAXN*4] ;
int tol[30][MAXN] ;
void Build(int idx, int l ,int r, int deep){
p[idx].l = l ; p[idx].r = r ;
if(l == r) return ;
int mid = MID(l, r) ;
int v= s[mid] ; //基准元素
int lnum = mid - l + 1 ; //(l,mid)中有多少个和基准元素相等的元素,即有多少个和基准元素相等元素可以入左子树
for(int i=l;i<=r;i++){
if( val[deep][i]<v ) lnum -- ;
}
int lto , rto ;
lto = rto = 0;
for(int i=l;i<=r;i++){
if(i == l) tol[deep][i] = 0 ;
else tol[deep][i] = tol[deep][i-1] ;
if(val[deep][i] < v){ //进入左子树
tol[deep][i] ++ ;
val[deep+1][ l+lto++ ] = val[deep][i] ;
}
else if(val[deep][i] > v){ //进入右子树
val[deep+1][ mid+1+rto++ ] = val[deep][i] ;
}
else if(val[deep][i] == v){ //相等时,需要判断是进入左子树还是右子树
if(lnum > 0){ //进入左子树
tol[deep][i] ++ ;
val[deep+1][ l+lto++ ] = val[deep][i] ;
lnum -- ;
}
else{ //进入右子树
val[deep+1][ mid+1+rto++ ] = val[deep][i] ;
}
}
}
Build( L(idx),l,mid ,deep+1) ;
Build( R(idx) ,mid+1,r,deep+1) ;
}
int query(int l,int r , int k , int idx,int deep){
if(l == r) return val[deep][l] ;
int sum ,ss;
if(l == p[idx].l) ss = 0 ;
else ss = tol[deep][l-1] ;
sum = tol[deep][r] - ss ;
int s = p[idx].l ;
int t = p[idx].r ;
int mid = MID(p[idx].l , p[idx].r) ;
if(sum >= k){
return query(s+ss, s+tol[deep][r]-1 , k, L(idx) , deep+1);
}
else{
int ee = l - s + 1 - ss ;
int e = r - l - sum ;
return query(mid+ee , mid+ee+e, k-sum , R(idx) ,deep+1);
}
}
int main(){
int a ,b ,c ;
while(scanf("%d %d",&N,&M) == 2){
for(int i=1;i<=N;i++){
scanf("%d",&d[i]);
val[1][i] = s[i] = d[i] ;
}
std::sort(s+1,s+1+N);
Build(1,1,N,1);
for(int i=0;i<M;i++){
scanf("%d %d %d",&a,&b,&c);
printf("%d\n",query(a,b,c,1,1));
}
}
return 0 ;
}