POJ 2104 K-th Number 划分树

来源:互联网 发布:无线传感器网络电子书 编辑:程序博客网 时间:2024/05/22 10:40

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 ;elsetol[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 ;elsess = 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 ;elsetol[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 ;elsess = 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 ;}


原创粉丝点击