zoukankan      html  css  js  c++  java
  • 「Luogu P2468 [SDOI2010]粟粟的书架」

    这道题分为两个部分

    Part1

    前置芝士

    1. 前缀和(后缀和,二维前缀和):可以预处理一下数据.
    2. 二分查找:可以在较短的时间内找出答案.

    具体做法

    可以发现(R,C)不大,只有(200),于是可以先预处理出一个数组(a[i][j][k]),表示从((1,1))~((i,j))中高度为k的书出现的次数,也可以理解为一个二维前缀和,考虑贪心,所以肯定是会优先去拿高的书,于是需要再处理两个数组(b[i][j][k]),表示((1,1))~((i,j))中高度在(k)~(1000)的范围内的书的出现次数,就是在(a)数组的基础上加上一个后缀和,以及(c[i][j][k]),表示((1,1))~((i,j))中高度在(k)~(1000)的范围内的书的总高度,处理方法和(b)数组类似.这样就可以在(O(1))的复杂度计算出拿取规定区域内的书中高度在(k)~(1000)的范围内的书的总高度和本数,计算方法与二维前缀和计算矩阵和类似(写这种题应该不会有人不会吧).接下来可以用一个二分,很快速得计算出需要取从多少高度开始的书,但是,这样计算出来的不是答案,这个高度的书未必就一定会全部取完,所以还需要将那些多取的书减去.

    代码

    long long arr[233][233][1024];
    int num[233][233][1024];
    long long sum[1024];
    long long Get(int fx,int fy,int lx,int ly,int num)//计算从(fx,fy)~(lx,ly)中num~1000的高度的书的高度和
    {
    	//计算方法为一个简单的容斥,如果实在不能理解建议先做一下P3138
    	return arr[lx][ly][num]-arr[fx-1][ly][num]-arr[lx][fy-1][num]+arr[fx-1][fy-1][num];
    }
    int GetCnt(int fx,int fy,int lx,int ly,int top)//计算从(fx,fy)~(lx,ly)中top~1000的高度的书的本书
    {
    	return num[lx][ly][top]-num[fx-1][ly][top]-num[lx][fy-1][top]+num[fx-1][fy-1][top];
    }
    void Cry()//没法摘到苹果...
    {
    	printf("Poor QLW
    ");
    }
    void Solve1()//解决第一部分
    {
    	scanf("%d",&T);
    	int high;
    	REP(i,1,N)
    	{
    		REP(j,1,1000)//先清空这个一维的前缀
    		{
    			sum[j]=0;
    		}
    		REP(j,1,M)
    		{
    			scanf("%d",&high);
    			sum[high]++;//在一维前缀中++
    			REP(k,1,1000)//当前的每个数出现的次数为这个位置上面的每个数出现的次数+这个一维前缀中每个数出现的次数
    			{
    				arr[i][j][k]=arr[i-1][j][k]+sum[k];
    			}
    		}
    	}
    	REP(i,1,N)
    	{
    		REP(j,1,M)
    		{
    			DOW(k,1000,1)
    			{
    				num[i][j][k]=num[i][j][k+1]+arr[i][j][k];//计算出k~1000中的数总共出现的次数
    				arr[i][j][k]=arr[i][j][k+1]+arr[i][j][k]*k;//计算出k~1000中的数总共的高度
    			}
    		}
    	}
    	int fx,fy,lx,ly,left,right,middle,answer,tot;
    	long long need,p;
    	REP(i,1,T)
    	{
    		scanf("%d%d%d%d%lld",&fx,&fy,&lx,&ly,&need);
    		if(Get(fx,fy,lx,ly,1)<need)//如果总共的高度也达不到要求自然是摘不到苹果了
    		{
    			Cry();
    		}
    		else
    		{
    			left=1,right=1000;//这是一个二分
    			while(left<=right)
    			{
    				middle=(right+left)>>1;
    				if(Get(fx,fy,lx,ly,middle)>=need)
    				{
    					answer=middle;//记录答案
    					left=middle+1;
    				}
    				else
    				{
    					right=middle-1;
    				}
    			}
    			tot=GetCnt(fx,fy,lx,ly,answer);//找到总共的出现次数
    			p=Get(fx,fy,lx,ly,answer);
    			while(p-answer>=need)//answer的值未必全部取,所以可能需要减去几个
    			{
    				p-=answer;
    				tot--;
    			}
    			printf("%d
    ",tot);//输出答案
    		}
    	}
    }
    

    Part2

    前置芝士

    1. 主席树:这可能才是本题的考点吧.

    具体做法

    在做本题前,建议先完成P3834,如果写过这道题那么思如应该会很清晰,用主席树维护一下前缀每个数出现的次数以及总高度,然后主席树上二分一下,然后...就没有然后了,几乎就是一个模板题了.

    代码

    int top=1000;
    int root[maxN];
    struct Tree
    {
    	int sum,lson,rson;
    	long long sum_;
    }tree[maxN*32];
    int point_cnt=0;
    //主席树标准define
    #define LSON tree[now].lson
    #define RSON tree[now].rson
    #define MIDDLE ((left+right)>>1)
    #define LEFT LSON,left,MIDDLE
    #define RIGHT RSON,MIDDLE+1,right
    #define NEW_LSON tree[new_tree].lson
    #define NEW_RSON tree[new_tree].rson
    void PushUp(int now)
    {
    	tree[now].sum=tree[LSON].sum+tree[RSON].sum;
    	tree[now].sum_=tree[LSON].sum_+tree[RSON].sum_;
    }
    void Updata(int num,int &new_tree,int now,int left=1,int right=top)//主席树修改,从now到new_tree这颗新的线段树中
    {
    	if(num<left||right<num)
    	{
    		new_tree=now;
    		return;
    	}
    	new_tree=++point_cnt;
    	if(left==right)
    	{
    		tree[new_tree].sum=tree[now].sum+1;//出现的次数+1
    		tree[new_tree].sum_=tree[now].sum_+num;//总共高度加num
    		return;
    	}
    	Updata(num,NEW_LSON,LEFT);//继续修改
    	Updata(num,NEW_RSON,RIGHT);
    	PushUp(new_tree);//合并
    }
    #define SUM tree[now].sum-tree[cut].sum
    #define SUM_ tree[now].sum_-tree[cut].sum_
    int Query(long long need,int cut,int now,int left=1,int right=top)//查询
    {
    	if(need<=0)//如果需要的小于等于0自然就是0了
    	{
    		return 0;
    	}
    	if(SUM_<=need)//如果需要的大于总共的则全部返回
    	{
    		return SUM;
    	}
    	if(left==right)//到也节点处理需要特殊一点
    	{
    		if(need%left==0)//如果可以除尽
    		{
    			return need/left;//直接返回
    		}
    		else
    		{
    			return need/left+1;//不可以则需要加1
    		}
    	}
    	return Query(need,											tree[cut].rson,RIGHT)//优先找大的,所以先找右子树
    		  +Query(need-tree[RSON].sum_+tree[tree[cut].rson].sum_,tree[cut].lson,LEFT);//在查找左子树的时候need需要减去右子树的部分
    }
    void Solve2()
    {
    	scanf("%d",&T);
    	int num;
    	REP(i,1,M)
    	{
    		scanf("%d",&num);
    		Updata(num,root[i],root[i-1]);//直接建树,维护前缀每个数出现的次数和总共高度
    	}
    	int left,right;
    	long long need;
    	int air;//没有用的数
    	REP(i,1,T)
    	{
    		scanf("%d%d%d%d%lld",&air,&left,&air,&right,&need);
    		if(tree[root[right]].sum_-tree[root[left-1]].sum_<need)//如果摘不到
    		{
    			Cry();
    		}
    		else
    		{
    			printf("%d
    ",Query(need,root[left-1],root[right]));//输出答案
    		}
    	}
    }
    
  • 相关阅读:
    谈谈对《镇魔曲》经济的一些看法
    谈谈对《神武2》经济的一些看法
    关于数值策划在使用Excel表时的一点想法
    游戏系统数值建模过程设计
    制作Excel工作薄目录
    制作当前表所在文件夹中所有文件的动态链接
    Excel各种tips汇总
    关于randbetween连乘的问题
    VBA实现两种方法生成任意概率分布的随机数
    Excel数值、文本相互转换
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/12300433.html
Copyright © 2011-2022 走看看