HDU 4251-The Famous ICPC Team Again(劃分樹-區間中位數)

kewlgrl發表於2016-05-07

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
 

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

 

相關文章