HDU 4417-Super Mario(劃分樹-二分查詢)

kewlgrl發表於2016-05-07

Super Mario

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4436    Accepted Submission(s): 2051


Problem Description
Mario is world-famous plumber. His “burly” figure and amazing jumping ability reminded in our memory. Now the poor princess is in trouble again and Mario needs to save his lover. We regard the road to the boss’s castle as a line (the length is n), on every integer point i there is a brick on height hi. Now the question is how many bricks in [L, R] Mario can hit if the maximal height he can jump is H.
 

Input
The first line follows an integer T, the number of test data.
For each test data:
The first line contains two integers n, m (1 <= n <=10^5, 1 <= m <= 10^5), n is the length of the road, m is the number of queries.
Next line contains n integers, the height of each brick, the range is [0, 1000000000].
Next m lines, each line contains three integers L, R,H.( 0 <= L <= R < n 0 <= H <= 1000000000.)
 

Output
For each case, output "Case X: " (X is the case number starting from 1) followed by m lines, each line contains an integer. The ith integer is the number of bricks Mario can hit for the ith query.
 

Sample Input
1 10 10 0 5 2 7 5 4 3 8 7 7 2 8 6 3 5 0 1 3 1 1 9 4 0 1 0 3 5 5 5 5 1 4 6 3 1 5 7 5 7 3
 

Sample Output
Case 1: 4 0 0 3 1 2 0 1 5 1
 

Source
 

Recommend
liuyiding   |   We have carefully selected several similar problems for you:  5679 5678 5677 5676 5675

題目意思:

求每組數中,在l~r區間內比h小的數的個數。

解題思路:

劃分樹+二分查詢。
直接可以用山東省第四屆ACM大學生程式設計競賽-Boring Counting
這個程式碼來改。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100005
using namespace std;

int sorted[4*N];   //排序完的陣列
int toleft[40][4*N];   //toleft[i][j]表示第i層從1到k有多少個數分入左邊
int tree[40][4*N];  //表示每層每個位置的值
int n;

void building(int l,int r,int dep)
{
    if(l==r)    return;
    int mid = (l+r)>>1;
    int temp = sorted[mid];
    int i,sum=mid-l+1;    //表示等於中間值而且被分入左邊的個數
    for(i=l; i<=r; i++)
        if(tree[dep][i]<temp)
            sum--;
    int leftpos = l;
    int rightpos = mid+1;
    for(i=l; i<=r; i++)
    {
        if(tree[dep][i]<temp)  //比中間的數小,分入左邊
            tree[dep+1][leftpos++]=tree[dep][i];
        else if(tree[dep][i]==temp&&sum>0)  //等於中間的數值,分入左邊,直到sum==0後分到右邊
        {
            tree[dep+1][leftpos++]=tree[dep][i];
            sum--;
        }
        else   //右邊
            tree[dep+1][rightpos++]=tree[dep][i];
        toleft[dep][i] = toleft[dep][l-1] + leftpos - l;   //從1到i放左邊的個數
    }
    building(l,mid,dep+1);
    building(mid+1,r,dep+1);
}

//查詢區間第k大的數,[L,R]是大區間,[l,r]是要查詢的小區間
int query(int L,int R,int l,int r,int dep,int k)
{
    if(l==r) return tree[dep][l];
    int mid = (L+R)>>1;
    int cnt = toleft[dep][r] - toleft[dep][l-1]; //[l,r]中位於左邊的個數
    if(cnt>=k)
    {
        int newl = L + toleft[dep][l-1] - toleft[dep][L-1]; //L+要查詢的區間前被放在左邊的個數
        int newr = newl + cnt - 1;  //左端點加上查詢區間會被放在左邊的個數
        return query(L,mid,newl,newr,dep+1,k);
    }
    else
    {
        int newr = r + (toleft[dep][R] - toleft[dep][r]);
        int newl = newr - (r-l-cnt);
        return query(mid+1,R,newl,newr,dep+1,k-cnt);
    }
}

int Search2(int L,int R,int l,int r,int b)
{
    int ans=0;
    while(l<=r)
    {
        int mid = (l+r)>>1;
        int res = query(1,n,L,R,0,mid);
        if(res>b)  //直到找到最後邊的大於b的結果
        {
            r = mid - 1;
            ans = mid;
        }
        else l = mid + 1;
    }
    if(!ans) return r;
    return ans-1;
}


int main()
{
    int t,cas = 1;
    scanf("%d",&t);
    while(t--)
    {
        int m;
        scanf("%d%d",&n,&m);
        int i;
        for(i=1; i<=n; i++)
        {
            scanf("%d",&tree[0][i]);
            sorted[i] = tree[0][i];
        }
        sort(sorted+1,sorted+1+n);
        building(1,n,0);
        int l,r,b;
        printf("Case %d:\n",cas++);
        while(m--)
        {
            scanf("%d%d%d",&l,&r,&b);
            ++l,++r;
            int x = 1;
            int y = r-l+1;
            printf("%d\n",Search2(l,r,x,y,b));
        }
    }
    return 0;
}

 注意題目給的時間是1000MS,用結構體的話會超時。。比如下面這個:

#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 t,cnt=0;
    scanf("%d",&t);
    while(t--)
    {
        printf("Case %d:\n",++cnt);
        int n,m,i,a,b,h;
        scanf("%d%d",&n,&m);
        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);//建樹
        for(i=1; i<=m; ++i)
        {
            //查詢
            scanf("%d%d%d",&a,&b,&h);
            ++a,++b;//二分查詢 找出h是區間內第幾小的數
            int l=0,r=b-a+2;
            while(r-l>1)
            {
                int mid=(l+r)>>1;
                if(query(1,n,0,a,b,mid)<=h)
                    l=mid;
                else r=mid;
            }
            printf("%d\n",l);
        }
    }
    return 0;
}


相關文章