牛客练习赛46----D-华华陪奕奕打怪兽

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/894/D
来源:牛客网
涉及:二分


题目如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
这一类的题目有一个主要的特点,就是答案的范围和输入的数据的范围具有很强的映射性,由于答案的范围已知,所以这种题目的做法就是二分答案,然后判断答案是否符合题目的要求和输入的数据特点,从而二分得到最合适的答案


为了二分答案ans,首先要知道ans的范围。

ans为题目要求的最小时间,即ans的值一定大于1,由于题目中给了a[i]=a[n-i]的条件,故无限序列a是一个循环序列,故每秒第j次攻击需要的精力数(a[i]*b[j])组成的二维数组平面也是一个循环的平面

举个例子,假如
a={2,4,5,6,2,4,5,6,2,4,5,6…}
b={1,2,3}
那么组成的每秒第j次攻击需要的精力数(a[i]*b[j])组成的平面为

精力数a[1]=2a[2]=4a[3]=5a[4]=6a[5]=2a[6]=4a[7]=5a[8]=6a[9]=2
b[1]=1245624 562
b[2]=2481012 4 8 10124
b[3]=36121518 61215186

称这个平面每一个循环节叫做一个循环面每一个循环面最少可能有一个a[i]*b[j](一次攻击)满足条件,由于需要c次攻击才算胜利,故ans的最大值不超过 n ∗ c n*c nc


对于每次二分得到的ans,表示在ans秒内完成了c次攻击,而每一秒可以完成多次攻击,我们可以把前ans秒内的a[i]*b[1](i<=ans)全部放入优先队列排序进行贪心的选取最小消耗的精力的某次攻击,如果选取了某个a[i]*b[1](即这一秒内攻击了一次),则将对应的a[i]*b[2]放入队列(表示这一秒可以进行下次攻击)。

但是注意,ans可能很大,把全部的a[i]*b[1](i<=ans)放入到队列里面可能会超时,由于a序列本来就是循环的,我们只需将一个循环的所有a[i]*b[1](i<=n)放入优先队列,然后每次选取到最小的a[k]*b[1]后,就判断一下这一个a[k]*b[1]在前ans秒内循环了几次,然后判断是否需要将前ans秒内所有的这次攻击(a[k]*b[1])全部选取(表示还需要攻击这么多次),然后将对应的a[i]*b[2]放入队列。

如果还需要攻击的次数timeq小于a[k][i]在前ans秒内循环的次数timep,则只需选取任意timeq个a[k]*b[1]即可。

注意剪枝:如果某次攻击a[i]*b[j]在前ans次内循环了0次,则可以直接重新在优先队列内进行选取,不需要将对应的a[i]*b[j+1]放入队列(反正循环次数都是0)。

PS:每次选择了一个a[i]*b[j]后要判断当前已经消耗了的精力是否大于等于初始精力w
1.如果攻击次数小于c但是消耗精力大于等于w表示ans太小了
2.如果已经消耗的精力小于w但攻击次数已经到达c表示ans太大了
3.当ans的左边界f大于右边界l时,有边界l+1就是答案
3.如果当左边界f大于右边界l时,且右边界l= c ∗ n c*n cn,则无解


举个例子:
比如n=3,c=2,w=3
a={4,5,1}
b={1,2}

1.开始左边界f=1,右边界l=c*n=6,ans=(6+1)/2=3
前3秒精力值消耗表如下(红色代表优先队列中的元素,加粗表示优先队列第一项,黑色表示未被选择,灰色表示已经被选择,括号内表示前ans秒a[i]*b[j]重复的次数):

精力数a[1]=4a[2]=5a[3]=1
b[1]=14(1)5(1)1(1)
b[2]=28(1)10(1)2(1)

选取全部的 a [ 3 ] ∗ b [ 1 ] a[3]*b[1] a[3]b[1],存入 a [ 3 ] ∗ b [ 2 ] a[3]*b[2] a[3]b[2]
此时精力消耗数sumw=1*1=1,攻击次数sumc=1(sumc<c,sumw<w,继续选择)

精力数a[1]=4a[2]=5a[3]=1
b[1]=14(1)5(1)1(1)
b[2]=28(1)10(1)2(1)

选取全部的 a [ 3 ] ∗ b [ 2 ] a[3]*b[2] a[3]b[2]
此时精力消耗数sumw=1+2*1=3,攻击次数sumc=2(sumw=w,停止选择)
此时ans=3不满足条件,表示ans可能太小了

2.左边界f=ans+1=4,右边界l=6,ans=(6+4)/2=5
前5秒精力值消耗表如下(红色代表优先队列中的元素,加粗表示优先队列第一项,黑色表示未被选择,灰色表示已经被选择,括号内表示前ans秒a[i]*b[j]重复的次数):

精力数a[1]=4a[2]=5a[3]=1
b[1]=14(2)5(2)1(1)
b[2]=28(2)10(2)2(1)

选取全部的 a [ 3 ] ∗ b [ 1 ] a[3]*b[1] a[3]b[1],存入 a [ 3 ] ∗ b [ 2 ] a[3]*b[2] a[3]b[2]
此时精力消耗数sumw=1*1=1,攻击次数sumc=1(sumc<c,sumw<w,继续选择)

精力数a[1]=4a[2]=5a[3]=1
b[1]=14(2)5(2)1(1)
b[2]=28(2)10(2)2(1)

选取全部的 a [ 3 ] ∗ b [ 2 ] a[3]*b[2] a[3]b[2]
此时精力消耗数sumw=1+2*1=3,攻击次数sumc=2(sumw=w,停止选择)
此时ans=5不满足条件,表示ans可能太小了

3.左边界f=ans+1=6,右边界l=6,ans=(6+6)/2=6
前6秒精力值消耗表如下(红色代表优先队列中的元素,加粗表示优先队列第一项,黑色表示未被选择,灰色表示已经被选择,括号内表示前ans秒a[i]*b[j]重复的次数):

精力数a[1]=4a[2]=5a[3]=1
b[1]=14(2)5(2)1(2)
b[2]=28(2)10(2)2(2)

选取全部的 a [ 3 ] ∗ b [ 1 ] a[3]*b[1] a[3]b[1],存入 a [ 3 ] ∗ b [ 2 ] a[3]*b[2] a[3]b[2]
此时精力消耗数sumw=1*2=2,攻击次数sumc=2(sumc=c,停止选择)
此时ans=6满足条件,表示ans可能太大了

4.左边界f=6,右边界l=ans-1=5
由于f>l,且l不等于 c ∗ n c*n cn,故答案ans=l+1=6

ps:题目中的例子刚好每次都选取了所有重复部分,还有可能只用选取一部分的重复部分


代码如下:

#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
typedef struct data{
	ll num;//num是消耗的精力值(a[i]*b[j])
	ll x;//x是第x秒
	ll y;//y是这一秒的第y次攻击
	data(ll a,ll b,ll c){
		num=a;x=b;y=c;
	}
}data;
struct cmp{
	bool operator()(const data p1,const data p2){
		return p1.num>p2.num;
	}
};
priority_queue<data,vector<data>,cmp> que,r_que;//优先队列以num从小到大为主键
ll n,c,w;//题目所给变量
int a[100005],b[100005];//题目所给变量数组
int main(){
	scanf("%lld%lld%lld",&n,&c,&w);
	int i;
	for(i=1;i<=n;i++)	scanf("%d",&a[i]);
	for(i=1;i<=c;i++)	scanf("%d",&b[i]);
	for(i=1;i<=n;i++)	r_que.push(data(a[i]*b[1],i,1));//r_que对=队列用来每次初始化que队列
	ll f=1,l=n*c,ans;//开始二分
	while(f<=l){
		que=r_que;//初始que队列
		ans=(f+l)/2;
		//timep指前ans秒所有num的最小重复次数
		//timeq+timep指前ans秒num的最大重复次数
		ll timep=ans/n,timeq=ans%n;//这一步用来计算当前ans所对应的最大和最小重复次数
		ll sumw=0,sumc=0;//sumw存已经消耗了的精力值,sumc存已经攻击的次数	
		while(1){
			data p=que.top();
			que.pop();
			ll time=(p.x<=timeq?timep+1:timep);//time存当前ans秒的当前num重复次数
			if(time==0)	continue;//重复次数为0,直接剪枝
			if(c-sumc>=time){//可以选择前ans秒的所有这次攻击(攻击是循环重复的)
				sumc+=time;
				sumw+=(p.num*time);
				if(p.y+1<=c)	que.push(data(a[p.x]*b[p.y+1],p.x,p.y+1));//将这一秒的下次攻击放入队列,供选择
			}
			else{//次数到达了c次,跳出选择
				sumw+=(c-sumc)*p.num;
				break;	
			}
			if(sumw>=w)	break;//如果消耗的精力大于等于初始精力值,已经不符合条件,跳出选择
		}
		if(sumw>=w)	f=ans+1;//如果消耗的精力大于等于初始精力值,表示ans太小了
		else	l=ans-1;//如果消耗的精力小于初始精力值,表示ans可能太大了
	}
	if(l==n*c && f>l)	return	!(cout<<"-1");//如果f>l,表示ans最终还是不符合条件,不存在ans值
	else	return !(cout<<l+1);
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值