HDU 4251-The Famous ICPC Team Again(划分树-区间中位数)

The Famous ICPC Team Again

Time Limit: 30000/15000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1254    Accepted Submission(s): 614



Problem Description
When Mr. B, Mr. G and Mr. M were preparing for the 2012 ACM-ICPC World Final Contest, Mr. B had collected a large set of contest problems for their daily training. When they decided to take training, Mr. B would choose one of them from the problem set. All the problems in the problem set had been sorted by their time of publish. Each time Prof. S, their coach, would tell them to choose one problem published within a particular time interval. That is to say, if problems had been sorted in a line, each time they would choose one of them from a specified segment of the line.

Moreover, when collecting the problems, Mr. B had also known an estimation of each problem’s difficultness. When he was asked to choose a problem, if he chose the easiest one, Mr. G would complain that “Hey, what a trivial problem!”; if he chose the hardest one, Mr. M would grumble that it took too much time to finish it. To address this dilemma, Mr. B decided to take the one with the medium difficulty. Therefore, he needed a way to know the median number in the given interval of the sequence.
 

Input
For each test case, the first line contains a single integer n (1 <= n <= 100,000) indicating the total number of problems. The second line contains n integers xi (0 <= xi <= 1,000,000,000), separated by single space, denoting the difficultness of each problem, already sorted by publish time. The next line contains a single integer m (1 <= m <= 100,000), specifying number of queries. Then m lines follow, each line contains a pair of integers, A and B (1 <= A <= B <= n), denoting that Mr. B needed to choose a problem between positions A and B (inclusively, positions are counted from 1). It is guaranteed that the number of items between A and B is odd.
 

Output
For each query, output a single line containing an integer that denotes the difficultness of the problem that Mr. B should choose.
 

Sample Input
   
   
   
   
5 5 3 2 4 1 3 1 3 2 4 3 5 5 10 6 4 8 2 3 1 3 2 4 3 5
 

Sample Output
   
   
   
   
Case 1: 3 3 2 Case 2: 6 6 4
 

Source
Fudan Local Programming Contest 2012
 

Recommend
We have carefully selected several similar problems for you:   4255  4247  4252  4246  4248

题目意思:

给出一组数,求给定区间内的中位数。

解题思路:

中位数k=(r-l)/2+1;//l和r是给定查询区间的两个端点。
然后直接套求求区间第k小的模板就可以啦!


#include <iostream>
#include <stdio.h>
#include <algorithm>
const int maxn = 100005;
using namespace std;
int sor[maxn];//借助sort排序的数组
struct node
{
    int num[maxn];//当前层的数
    int cnt[maxn]; //核心部分,保存每一个元素的左边的元素中位于下一层左子树的个数
} tree[40];//40是树的层数
//建树代码如下
void buildtree(int l, int r, int d)//d是深度
{
    if (l == r) return; //递归出口
    int mid = (l+r)>>1;//划分左右区间
    int opleft = l, opright = mid+1;//对左右子树的操作位置的初始化
    int same_as_mid = 0;//和sor[mid]相同的数的数目
    //计算在mid左边有多少个和sor[mid]相同的数(包括mid),都要放到左子树
    for (int i = mid; i > 0; i--)
    {
        if (sor[i] == sor[mid])  same_as_mid++;
        else    break;
    }
    int cnt_left = 0;//被划分到左子树的个数
    for (int i = l; i <= r; i++)
    {
        //从l到r开始遍历
        if (tree[d].num[i] < sor[mid])//左
        {
            tree[d+1].num[opleft++] = tree[d].num[i];
            cnt_left++;
            tree[d].cnt[i] = cnt_left;
        }
        else if(tree[d].num[i] == sor[mid] && same_as_mid)
        {
            //相同的都放在左子树
            tree[d+1].num[opleft++] = tree[d].num[i];
            cnt_left++;
            tree[d].cnt[i] = cnt_left;
            same_as_mid--;
        }
        else//右
        {
            tree[d].cnt[i] = cnt_left;
            tree[d+1].num[opright++] = tree[d].num[i];
        }
    }
    buildtree(l, mid, d+1); //递归建树
    buildtree(mid+1, r, d+1);
}
int query(int l, int r, int d, int ql, int qr, int k) //在d层[l,r]的节点里查找[a,b]中的第k小值
{
    if (l == r) return tree[d].num[l]; //递归出口
    int mid = (l+r)>>1;
    int sum_in_left;//区间内元素位于下一层左子树的个数
    int left;//[l,ql-1]左边的元素中位于下一层左子树的个数
    if (ql == l)
    {
        //如果ql是节点的左边界则有cnt[qr]个数进入左子树
        sum_in_left = tree[d].cnt[qr];
        left = 0;
    }
    else
    {
        //如果ql不是节点的左边界则有cnt[qr]-cnt[ql-1]个数进入了左子树
        sum_in_left = tree[d].cnt[qr] - tree[d].cnt[ql-1];
        left = tree[d].cnt[ql-1];
    }
    if (sum_in_left >= k)
    {
        //要找的点在左子树
        //确定下一步询问的位置:
        //如果在ql的左边有left个进入左子树
        //那么ql到qr中第一个进入左子树的必定在l+left的位置
        int new_ql = l+left;
        int new_qr = new_ql+sum_in_left-1;
        return query(l, mid, d+1, new_ql, new_qr, k);
    }
    else//要找的点在右子树
    {
        //确定下一步询问的位置
        int a = ql - l - left;//表示当前区间左半部分即[l,ql-1]中在下一层是右孩子的个数
        int b = qr - ql + 1 - sum_in_left;//表示当前区间右半部分即[ql,qr]中在下一层是右孩子的个数
        int new_ql = mid + a + 1;
        int new_qr = mid + a + b;
        //k-sum_in_left表示要减去区间里已经进入左子树的个数
        return query(mid+1, r, d+1, new_ql, new_qr, k - sum_in_left);
    }
}
int main()
{
    int n,m,i,a,b,k,cnt=0;
    while(~scanf("%d",&n))
    {
        printf("Case %d:\n",++cnt);
        for(i=1; i<=n; ++i)
        {
            scanf("%d",&sor[i]);//先插入到sor数组
            tree[0].num[i]=sor[i];//再插入第一层
        }
        sort(sor+1,sor+n+1);//升序排列
        buildtree(1,n,0);//建树
        scanf("%d",&m);
        for(i=1; i<=m; ++i)
        {
            //查询
            scanf("%d%d",&a,&b);
            k=(b-a)/2+1;//k是区间ab的中位数
            printf("%d\n",query(1,n,0,a,b,k));
        }
    }
    return 0;
}

 

你可能感兴趣的:(C++,ICPC,HDU,team,the,划分树,famous,4251,区间中位数)