本文主要是介绍牛客练习赛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]=2 | a[2]=4 | a[3]=5 | a[4]=6 | a[5]=2 | a[6]=4 | a[7]=5 | a[8]=6 | a[9]=2 |
---|---|---|---|---|---|---|---|---|---|
b[1]=1 | 2 | 4 | 5 | 6 | 2 | 4 | 5 | 6 | 2 |
b[2]=2 | 4 | 8 | 10 | 12 | 4 | 8 | 10 | 12 | 4 |
b[3]=3 | 6 | 12 | 15 | 18 | 6 | 12 | 15 | 18 | 6 |
称这个平面每一个循环节叫做一个循环面,每一个循环面最少可能有一个a[i]*b[j](一次攻击)满足条件,由于需要c次攻击才算胜利,故ans的最大值不超过 n ∗ c n*c n∗c
对于每次二分得到的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 c∗n,则无解
举个例子:
比如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]=4 | a[2]=5 | a[3]=1 |
---|---|---|---|
b[1]=1 | 4(1) | 5(1) | 1(1) |
b[2]=2 | 8(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]=4 | a[2]=5 | a[3]=1 |
---|---|---|---|
b[1]=1 | 4(1) | 5(1) | 1(1) |
b[2]=2 | 8(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]=4 | a[2]=5 | a[3]=1 |
---|---|---|---|
b[1]=1 | 4(2) | 5(2) | 1(1) |
b[2]=2 | 8(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]=4 | a[2]=5 | a[3]=1 |
---|---|---|---|
b[1]=1 | 4(2) | 5(2) | 1(1) |
b[2]=2 | 8(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]=4 | a[2]=5 | a[3]=1 |
---|---|---|---|
b[1]=1 | 4(2) | 5(2) | 1(2) |
b[2]=2 | 8(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 c∗n,故答案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);
}
这篇关于牛客练习赛46----D-华华陪奕奕打怪兽的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!