POJ 2104 K-th Number 划分树

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 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值