题目描述
小明为了掩护大部队,单枪匹马同敌人周旋,后来被敌人包围在某山头……等等,为什么怎么听怎么像狼牙山五壮士!不过不用着急,这次小明携带了足够的弹药,完全可以将涌上来的敌人一个一个干掉。小明是个神枪手,只要他的枪膛中有子弹,他就能将在他射程m(用从敌人位置到山头的直线距离算)以内的一个敌人瞬间射杀。但如果在射程内没有敌人,出于节约子弹考虑和面子问题,小明会等待敌人靠近然后射击。
正当小明为自己的强大而自我膨胀时,他忽然发现了一个致命的失误:他携带的枪是单发枪,每射出一发子弹都必须花k秒钟的时间装子弹。而凶残的敌人才不会花时间等你换子弹呢。他们始终在以1m/s的速度接近山头。而如果在一个敌人到达山头时小明无法将他击毙,那么我们可怜的小明就将牺牲在敌人的刺刀下。现在小明用心灵感应向你发出求助:要保住自己的性命并且歼灭所有敌人,小明最多只能用多少时间给枪装上一发子弹?
说明:假设一开始小明的枪中就有一发子弹,并且一旦确定一个装弹时间,小明始终会用这个时间完成子弹的装卸。希望你能帮助小明脱离险境。
输入格式
每组输入数据,第一行有两个整数n和m,(2≤n≤100,000; 1≤m≤10,000,000)n代表敌人个数,m代表小明的射程。
接下来有n行,每行一个整数mi,(1≤mi≤10,000,000),代表每个敌人一开始相对山头的距离(单位为米)。
输出格式
每组输出数据仅有一个整数,代表小明的换弹时间(单位为秒)。
样例输入
6 100
236
120
120
120
120
120
样例输出
25
解题思路:
一看到关于换弹时间的要求:在尽量长的同时不能超过一个值,就可以想到用二分法
考虑到题中并未说明输入的数据是有序的,我们需要输入数据进行排序
那么采用什么方法排序呢,是输入和排序同时进行?还是利用数组加sort()函数?
暂时无法确定
那么首先,二分法的实现如下
int left = 0, right = int(1e7) + 1, middle;
while (left + 1 != right)
{
middle = (left + right) / 2;
if (is_die(middle))//小明成功存活,判断换弹时间是否能够更长
{
max_time = middle;
left = middle;
}
else//小明被做掉了,换弹应该快一些
{
right = middle;
}
}
然后剩下的问题就是实现一个函数用于middle的判断了
参数列表:唯一需要传入的参数即为middle也就是换弹时间
返回值类型:需要返回bool类型值,因为返回值的作用只是判断接下来left = middle还是right = middle
本题的核心在于函数体的实现
先理解题意,什么样的换弹时间符合条件
换弹时间应该让小明能够存活下来,那么怎么判断小明是否能够存活
首先我们会建立这样一个模型,小明坐标始终为0,所有敌人不断前进,判断最近的敌人是否能够杀死小明
我们发现了,这里也同样要求敌人的位置是有序的,我们同样之后再说明
如何实现所有敌人不断前进?去改变所有敌人的坐标显然不是一个明智的选择
那么我们先说明第一种方法
用一个变量去累计时间,如果不是最近的敌人,先让他不要动,等轮到他了,他就直接瞬移一段距离
这种方法可行,但这里不予以实现,我们接下来要说明第二种方法
问题要抓住本质,本质上就是小明和敌人之间的距离不断变小的过程
那么换弹时间就可以转化为距离的变化,同样,不需要改变敌人的位置,我们可以改变小明的位置
那么现在题意完成了转化:
现在有一堆静止的靶子,小明每走一段距离才能开一枪
那么现在进一步考虑如何判断小明是否会遇到靶子的问题
为了避免TLE,我们首先考虑,能否一次性计算小明能否存活?
射程就是一个问题,那么再进一步,能否一次性计算射程内的所有敌人所需要的最短换弹时间?
如果1个敌人在int(1e7)处,int(1e5)-1个敌人就在1处,显然我们不需要考虑最远的那个敌人,只要能干掉面前的敌人,小明就能够成功存活
那么就只能采用遍历的方式了,我们现在来说明循环的实现
循环结束条件:当然就是敌人全部被干掉或者小明被干掉
然后说明循环体,我们需要考虑,射程内有没有敌人(当然这时候枪里有子弹,否则有没有敌人都是无意义的)
没有敌人的话,我们需要继续前进,直到有敌人进入射程
有敌人的话,我们需要判断小明能否存活
到这里了我们一定知道如何判断:小明干掉所有敌人需要前进的距离是否小于小明与敌人间的距离(为什么相等时小明能够存活呢,因为题中说小明能够瞬间干掉敌人)
不小于的话,那么由于换弹太慢,小明被干掉了
小于,敌人被干掉,我们继续下一轮循环
至此,函数实现完毕,以下是函数功能的实现
bool is_die(int bullet_time)//判断小明是否会die
{
//遍历enemy_map
int xm_index = 0;//小明的位置(注:每轮都需要初始化)
map<int, int>::iterator step = enemy_map.begin();
while (step != enemy_map.end())//干掉每一个敌人
{
if (step->first - xm_index > shoot_distance)//如果射程内没有敌人
{
xm_index += step->first - xm_index - shoot_distance;//向前进,直到敌人进入射程
}
else//如果射程内有敌人
{
if (step->first - xm_index < 0)//哦,原来敌人已经把我干掉了
{
//出现这种情况是因为在确认存活并换弹的过程中,有敌人把小明干掉了
return false;
}
if ((step->second - 1) * bullet_time <= step->first - xm_index)//如果小明不能走到敌人的面前
{
//注意在判断时,不用考虑杀死最后一个敌人后的换弹时间
xm_index += step->second * bullet_time;//确认存活,再换弹
step++;
}
else//如果小明会die
{
return false;
}
}
}
//循环退出,小明存活
return true;
}
到了这里,我想已经不用我去说怎么排序了,但还是做一下说明
排序的要求就是按照位置排序,每个位置要带有敌人数量的记录
其实就是存储并排序一对数据,那么很容易想到借助STL中的map
key存储位置,自动排序
value存储敌人数量
至此,本题实现完成
本题完整的代码如下
#include <iostream>
#include <map>
using namespace std;
map<int, int> enemy_map;//key用于存储敌人位置,value用于存储该位置的敌人数量
int shoot_distance;//射程
bool is_die(int bullet_time)//判断小明是否会die
{
//遍历enemy_map
int xm_index = 0;//小明的位置(注:每轮都需要初始化)
map<int, int>::iterator step = enemy_map.begin();
while (step != enemy_map.end())//干掉每一个敌人
{
if (step->first - xm_index > shoot_distance)//如果射程内没有敌人
{
xm_index += step->first - xm_index - shoot_distance;//向前进,直到敌人进入射程
}
else//如果射程内有敌人
{
if (step->first - xm_index < 0)//哦,原来敌人已经把我干掉了
{
//出现这种情况是因为在确认存活并换弹的过程中,有敌人把小明干掉了
return false;
}
if ((step->second - 1) * bullet_time <= step->first - xm_index)//如果小明不能走到敌人的面前
{
//注意在判断时,不用考虑杀死最后一个敌人后的换弹时间
xm_index += step->second * bullet_time;//确认存活,再换弹
step++;
}
else//如果小明会die
{
return false;
}
}
}
//循环退出,小明存活
return true;
}
int main()
{
int max_time = 0;//存储最大时间
int n;
cin >> n >> shoot_distance;//读入敌人个数、射程
int temp_key;//用于读入敌人的位置
map<int, int>::iterator is_exist;
for (int i = 0; i < n; i++)//读取敌人信息
{
cin >> temp_key;
is_exist = enemy_map.find(temp_key);
if (is_exist == enemy_map.end())//若当前位置没有敌人
{
enemy_map.insert(make_pair(temp_key, 1));
}
else//当前位置有敌人
{
is_exist->second += 1;//敌人数量+1
}
}
int left = 0, right = int(1e7) + 1, middle;
while (left + 1 != right)
{
middle = (left + right) / 2;
if (is_die(middle))//小明成功存活,判断是否换弹时间能够更长
{
max_time = middle;
left = middle;
}
else//小明被做掉了,换弹应该快一些
{
right = middle;
}
}
cout << max_time << endl;
return 0;
}