POJ 2104 K-th Number【划分树】


http://poj.org/problem?id=2104
POJ 2104 K-th Number
大意:有n个数字排成一列,有m个询问,格式为:
left right k
即问在区间[left,right]第k大的数据为多少?

分析:划分树
具体的介绍开心->OK介绍得很清楚,这里把他PPT里的部分贴上来,邪恶一把,嘿嘿。。
建图:
建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数a[mid],小于a[mid]的数划入它的左子树[l,mid-1],大于它的划入右子树[mid,r]。
同时,对于第i个数a[i],记录在[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间[l,mid-1]和右子树区间[mid,r]递归的继续建树就可以了。
建树的时候要注意,对于被分到同一子树的元素,元素间的相对位置不能改变。

查找的过程中主要问题就是确定将要查找的区间。
查找深度为dep,在大区间[L ,R]中找小区间[l ,r]中的第k元素。
我们的想法是,先判断[l ,r]中第k元素在[L ,R]的哪个子树中,然后找出对应的小区间和k,递归的进行查找,直到小区间的l==r为止。
通过之前的记录可以知道,在区间[L,l-1]中有(toleft[dep][l-1]-toleft[dep][L-1])进入左子树,
记它为x。

同理区间[L,r]中有(toleft[dep][r]-toleft[dep][L-1])个数进去左子树,记它为y。
所以,我们知道区间小区间[l,r]中有(y-x)个数进入左子树。那么如果(y-x)>=k,那么就在左子树中继续查找,否则就在右子树中继续查找。

  接着,解决查找的小区间的问题。
  如果接下来要查找的是左子树,那么小区间应该是[L+([L,l-1]区间进入左子树的个数),L+([L,r]区间内进入左子树的个数)-1]。即区间[L+x,L+y-1]。
    显然,这里k不用变。
  如果接下来要查找的是右子树,那么小区间应该是[mid+([L,l-1]区间中进入右子树的个数),mid+([L,r]区间进入右子树的个数)-1]。
    即区间[mid+(l-L-x),mid+(r-L-y)]。
    显然,这里k要减去区间里已经进入左子树的个数,即k变为k-(y-x)。

ContractedBlock.gif ExpandedBlockStart.gif POJ 2104 Code
 
   
#include < stdio.h >
#include
< string .h >
#include
< algorithm >
using namespace std;
const int MAXN = 100000 + 10 ;
int tree[ 22 ][MAXN],toleft[ 22 ][MAXN];
int sorted[MAXN]; // 已经排好序的数据
void build_tree( int left, int right, int deep) // 建树
{
if (left == right) return ;
int mid = (left + right) >> 1 ;
int i;
int same = mid - left + 1 ; // 位于左子树的数据
for (i = left;i <= right;i ++ ) // 计算放于左子树中与中位数相等的数字个数
{
if (tree[deep][i] < sorted[mid])
same
-- ;
}

int ls = left;
int rs = mid + 1 ;
for (i = left;i <= right;i ++ )
{
int flag = 0 ;
if ((tree[deep][i] < sorted[mid]) || (tree[deep][i] == sorted[mid] && same > 0 ))
{
flag
= 1 ;
tree[deep
+ 1 ][ls ++ ] = tree[deep][i];
if (tree[deep][i] == sorted[mid])same -- ;
}
else
{
tree[deep
+ 1 ][rs ++ ] = tree[deep][i];
}

toleft[deep][i]
= toleft[deep][i - 1 ] + flag;
}

build_tree(left,mid,deep
+ 1 );
build_tree(mid
+ 1 ,right,deep + 1 );
}

int query( int left, int right, int k, int L, int R, int deep)
{
if (left == right) return tree[deep][left];
int mid = (L + R) >> 1 ;
int x = toleft[deep][left - 1 ] - toleft[deep][L - 1 ]; // 位于left左边的放于左子树中的数字个数
int y = toleft[deep][right] - toleft[deep][L - 1 ]; // 到right为止位于左子树的个数
int ry = right - L - y; // 到right右边为止位于右子树的数字个数
int cnt = y - x; // [left,right]区间内放到左子树中的个数
int rx = left - L - x; // left左边放在右子树中的数字个数
if (cnt >= k)
return query(L + x,L + y - 1 ,k,L,mid,deep + 1 );
else
return query(mid + rx + 1 ,mid + 1 + ry,k - cnt,mid + 1 ,R,deep + 1 );

}

int main()
{
int n,m;
while (scanf( " %d%d " , & n, & m) == 2 )
{
int i;

for (i = 1 ;i <= n;i ++ )
{
scanf(
" %d " , & sorted[i]);
tree[
0 ][i] = sorted[i];
}

sort(sorted
+ 1 ,sorted + n + 1 );
build_tree(
1 ,n, 0 );
while (m -- )
{
int left,right,k;
scanf(
" %d%d%d " , & left, & right, & k);
printf(
" %d\n " ,query(left,right,k, 1 ,n, 0 ));
}
}
return 0 ;
}

转载于:https://www.cnblogs.com/AndreMouche/archive/2011/03/04/1971291.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值