france

https://github.com/francecil

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理


http://acm.hdu.edu.cn/showproblem.php?pid=4417

Super Mario

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


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
 

题目大意:在区间[s,t]查找不大于h的有多少个

做法:用划分树二分枚举第K大(具体看下面注释)

注释处有分析在区间[s,t]查找大于h的有多少个 注意融会贯通


#include <iostream>
#include <string.h>
#include <algorithm>
#define maxn 100010
#define mid ((L+R)>>1)
using namespace std;
int tree[20][maxn];//表示每层每个位置的值
int toleft[20][maxn];//20层每层maxn t用来放原序; toleft[p][i]表示第P层第i个放左节点的元素个数
int sorted[maxn];//已经排序的数
//以下为查找区间第k小划分树
void build(int p,int L,int R) //p:第几层 默认0开始  ; L,R 左右区间从[1,n]开始建
{
    if(L==R) return;  //这个放最上边省时
    int lm=0,i,ls=L,rs=mid+1;//lm表示应被放入左子树且与中位数相等的数有多少个,ls为左子树的起始位置,rs为右子树的起始位置
    for(i=mid;i>=L;i--) //求lm ;2 3 3 4 4 5 5 7 9得到的lm=2
    {
        if(sorted[i]==sorted[mid])
        lm++;          //之前有一个错误的想法: 不记录这个lm  找的时候直接找<=的  但是这样会出错  比如 2 4 4 4 5 1
        else         // 排序: 1 2 4 4 4 5   那如果我们直接找<=的(6/2=3)个的话 会找到 2 4 4这样就错了所以还是要记录lm
        break;
    }
    for(i=L;i<=R;i++)
    {
        if(i==L)//这里要特殊讨论(原因:间接的对所有初始化 我们这样做便于toleft[p][i]=toleft[p][i-1]这一部的处理)
        toleft[p][i]=0;     //
        else
        toleft[p][i]=toleft[p][i-1];//下一个肯定是上一个+0或1

        if(tree[p][i]==sorted[mid])//若与中位数相等则判断是否应该被放入左子树
        {
            if(lm)
            {
                lm--;
                toleft[p][i]++;  //如果满足 说明又多了一个元素放左节点了
                tree[p+1][ls++]=tree[p][i];//放入下一个t[]
            }
            else
            tree[p+1][rs++]=tree[p][i];
        }
        else if(tree[p][i]<sorted[mid])//查找区间第K大即为>
        {
            toleft[p][i]++;
            tree[p+1][ls++]=tree[p][i];
        }
        else
        tree[p+1][rs++]=tree[p][i];
    }
    build(p+1,L,mid);
    build(p+1,mid+1,R);
}
//查询区间第k大的数,[L,R]是大区间,[l,r]是要查询的小区间
int query(int p,int L,int R,int l,int r,int k)
{
    int s,ss;//s表示[L,l-1]放入左子树的个数,ss表示区间[l,r]被放入左子树的个数
    if(L==R)//找到所求的数
    return tree[p][L];
    if(l==L)
    s=0,ss=toleft[p][r];
    else
    s=toleft[p][l-1],ss=toleft[p][r]-s;
    if(k<=ss)//要找的数在左子树中
    return query(p+1,L,mid,L+s,L+toleft[p][r]-1,k);
    else//要找的数在右子树中
    return query(p+1,mid+1,R,mid+1-L+l-s,mid+1-L+r-toleft[p][r],k-ss);
}
//原理 在区间[s,t]查找不大于h的有多少个,我们先让区间第mid2小的数与他比较;
//如果这个数还小于h 说明现在至少有mid2个数比h小 然后找[(mid2+1)+r]>>1大的数继续比较;
//否则说明这个数比h大;不能判断至少多几个比h小;所以找[l+mid2]>>1大的数继续比较;
int solve(int L,int R,int s,int t,int h){  //在查找区间[s,t]不大于h的个数有多少[L,R]<=>[1,n]
	int l=1;
	int r=t-s+1;
	int mid2;
	int ans=0;
	while(l<=r){
		mid2=(l+r)>>1;
		int temp=query(0,L,R,s,t,mid2);//得到区间【s,t】的第mid2小的数
		/*
				 //如果问在查找区间[s,t]大于h的个数有多少
				 // 方法一:
				 if(temp>h){  //if第mid2小的数都比h来得大那至少有(t-s+1)-mid2+1个
				  ans=(t-s+1)-mid2+1;//自己画一下就能够理解了
				  r=mid2-1;
		        	}
			    else l=mid2+1;//if第mid2小的数不大于h 往后面找
			    //方法2:
			     * query方法改成得到区间【s,t】的第mid2 ****大*****的数(上面有讲只需要改个符号即可)
				  if(temp>h){  //if第mid2大的数h大;那至少有mid2个数比h大
				  ans=mid2//
				  l=mid2+1;//往后找
		        	}
			    else r=mid2-1;//否则无法知道至少几个,往前面找更大的数与之比较

		 */
		if(temp<=h){
			ans=mid2;
			l=mid2+1;
		}
		else r=mid2-1; //注意这里要-1 不然答案出不来 因为while的条件是l<=r 如果不-1会出现 l=r的情况 mid2=l=r无限循环
	}
	return ans;
}
int main()
{
	int T;
	    int n,m;
	    int s,t,h;
	   cin>>T;
	    int iCase=0;
	    while(T--)
	    {

	        cin>>n>>m;
	        for(int i=1;i<=n;i++)//从1开始
	        {
	            cin>>tree[0][i];
	            sorted[i]=tree[0][i];
	        }
	        sort(sorted+1,sorted+n+1);
	        build(0,1,n);
	        cout<<"Case "<<++iCase<<":"<<endl;
	        while(m--)
	        {
	            cin>>s>>t>>h;
	            s++; //题目的输入是从[0,n-1] 我们数据转化到[1,n]
	            t++;
               cout<<solve(1,n,s,t,h)<<endl;
	        }
	    }
	    return 0;
}
/*
 *
 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
output:
Case 1:
4
0
0
3
1
2
0
1
5
1

 * */


版权声明:本文为博主原创文章,未经博主允许不得转载。

posted on 2014-07-29 10:23  france  阅读(324)  评论(0编辑  收藏  举报