一起看题目:
现在有一只青蛙 要过河,过河路线可以看成一个数轴,起点为0,终点为M。
河里面有N块石头,青蛙只能跳在石头上(或者在两岸),它一次能跳的最大距离是L。
小青蛙想少跳,而你想让他多跳。
小青蛙有可能跳不过去某个石头,因为离下一个石头太远了,所以给你特殊能力,你可以在河里面任意位置放石头,以此来帮助青蛙成功跨过距离太远的石头。
现在让你观察河里石头的情况,你可以预测在哪个地方青蛙跳不过去,然后你来放石头,确保青蛙一定能跳到对岸,聪明的青蛙会在你放完石头后才出发。
(你想让青蛙尽可能多跳几次,而青蛙是聪明的,它会尽可能少跳)
每组测试数据包括N,M,L
然后是N个数,代表石块在数轴上的位置。
输出小青蛙跳的次数
首先这N块石头的位置可能是乱序的,所以先给石子按照位置从小到大排序,并且在最后添加一个石子,它的位置是M,代表对岸(也就是终点)的位置。
设当前青蛙在now处,上一步在pre处
【分析情况】
(为方便分析:设青蛙现在在位置0处,假设它一次最多跳L = 3个,并且先不考虑终点M,只考虑在河中的过程)
1:青蛙可以顺利地跳下去,我们们不需要帮它。
(下图:黑色的位置有石头,先跳到2,然后青蛙想少跳,所以忽略4,跳到5)
2.第二种情况,青蛙在0就跳不了了,如下图:
(请思考,前两个石头你要怎么放,先考虑前两个,之后的等会再说)
因为想让青蛙多跳,所以尽可能地把石头放近一点,那第一块石头放在1,青蛙会跳到1。青蛙跳到1之后:now = 1 pre = 0———— ①
第二块石头呢? 是不是要放在2? ———— ②
如果你放在2,不要忘了,聪明的青蛙在你放完石头之后才出发,如果先进行①,又进行②, 那青蛙可以直接跳到2,而忽略位置1的石头。如下图:
所以②是不对的,②放的太近了,所以青蛙直接忽略①中放的石头了
我们应该稍微放远一点,放在位置4———— ③
为什么呢?因为如果放在2或者3,青蛙都可以忽略位置1的石头,直接跳到2 或者3, 也就是应该放在pre + L + 1处(这样青蛙就不会忽略中间放的那块石头了,它必须先跳到中间那块石头上,然后跳到pre+L+1处)
放石头的时候,不要放没用的石头,要保证青蛙不会忽略中间放的那块石头,这就是很多人说的1+L的方式放石头,先放在距离1处,再放在距离1+L处,如下图:河里一块石头都没有,应该怎么放石头—— 按照1,L的方1 3 1 3……
3.第三种情况,刚才都是从0开始的,现在不从0开始,假设青蛙已经跳了几步了,如下图:在2处石头,青蛙跳到了2,然后4有石头,跳到了4,之后没石头了,接下来怎么放呢?
是不是要按照1,L 1 L 1 L的方式呢?
如果按照1 L 1 L,那下一块放在5,然后放在8 ……但是,5如果有石头,青蛙就从2跳到5了,它会忽略中间那块4,刚刚谈到过这个情况,放石头不能让青蛙忽略中间的石头,所以放在pre + L + 1 = 2 + 3 + 1 = 6处,之后放在哪先不管,等会再说
4.再看一下 下面这个情况:
这个图和上一个图的区别就是:这个最后在5,上一个最后在4
现在看这种情况,下一个石头放在哪呢?
这种情况2 和 5 差了3个,青蛙最多就跳3个,所以青蛙必须从2跳到5,它不会忽略5的,所以按照1 L 1 L 放, 也就是放在6,9,10,13,14,17……,
看下面这句话:加石子要加在pre刚好不能跳到,而now能跳到的最近的那一个
pre最远跳pre + L,所以pre刚好跳不到的那个点是pre+L+1,
这个点就是放石子的点
5.现在考虑这种情况:pre = 0, now = 2, L = 4(一次跳4个);如图
下一块石头放在哪?pre + L + 1 = 0 + 4 + 1 = 5; 然后pre = 2, now = 5;
继续,下一块石头放在pre + L + 1 = 2 + 4 + 1 = 7;.……如下图:
如果一直重复下去,这个不是按照 1 L 1 L 1 L 放石头
而是每隔3个 2个 3个 2个 3个 2个…… 放一个石头
但是 : 3 + 2 = 1 + L = 5; 所以 只要每隔 1 + L 个位置,都要跳两次,
比如上图,0 – 5,隔了5个位置, 跳两次, 2 – 7,隔了5个位置,跳两次
可以这样解释:从pre 跳到now,1次; 从 now 跳到 pre + L + 1 ,又一次
中间隔了 pre + L + 1 – pre = 1 + L 个间隔
之后pre 和now 更新,继续重复下去
6.以上问题化简了不少东西,比如没考虑终点,现在讨论有终点的情况:
按照5中石头的摆放方式,假设8是终点,那么青蛙在5的时候,应该忽略7,直接跳到8。如果9是终点,也应该忽略7,直接跳到9。
最后一步怎么判断用不用忽略某个石头呢?看下图情况:
初始情况:N = 1(在2有石头),M = 20,21,22(多讨论几种情况),L = 4,
青蛙跳到2,now = 2,pre = 0.然后按照pre + L + 1,更新now,pre重复下去
中间有重复的2 3 2 3 2 3,我们可以先数一下中间经过了几个2 + 3(也就是1+L),设int cishu;
cishu = (下一块石头的位置 - now) / (1 + L);
上图情况就是cishu = (20 - 2) / 5 = 3 余3
所以int yushu;
yushu = (下一块石头位置 - now) % (1 + L);
这种情况就解完了
现在假设M = 19, 最后一步怎么跳?应该忽略17,从15直接跳到19.
对于这种情况: cishu = (19 - 2) / 5 = 3;
yushu = 2; 从12 到 17 是完整的一个3+ 2 (1 + L),但是余数刚好可以拼凑到2里面,也就是最后从15到17 和从17 到19,可以拼成一步,上一种情况余数是3,余数的上一步跳了2, 3和2超过了4,所以多跳一步。
所以判断最后一步用不用合并,只要看余数和余数的上一步加起来是不是能一步跳过去
现在的问题是,余数的上一步是多少?
终点还是19,从2 到19 经过了3个完整的 2 + 3(1 + L),余下2个,
判断余下的2个,和余数的上一步 2 能不能合并跳
这个余数的上一步是怎么来的呢?——是 now – pre = 2 – 0得到的
放石头的位置是pre刚好达不到,且now能到的最近位置,这一点决定了是按照
1 + 4 或者 2 + 3 ……的方式放石头,但是1+4还是2+3,完整的1次的长度加起来还1+L
余数的上一步由now – pre决定,体现在代码中用上次跨越的距离(distance)表示了
if(distance + yushu > L) sum += cishu * 2;
else sum += cishu * 2 + 1;
还有一种情况:如果在2,19有石头,终点是26,这种情况可以由以下两种情况组合:
把19当终点。
把19当起点,看和终点有几个1+L,是一个重复的过程
还有一种情况:
初始distance = 0,sum = 0;
青蛙可以跳4步,1 2 3 4 6都有石头,
它应该直接跳到4,但是我们考虑的是
cishu 和yushu 和yushu + distance是否 > L
所以让它跳到1,cishu = 0,yushu = 1, yushu + distance = 1,
所以可以合并:
{
sum += 2 * cishu = 0;
///但是distance也要更新,distance 应该合并之后跳的步数
distance += yushu;
}
还要考虑的情况是第一步的问题,第一次如果yushu > 0,一定要多跳一步,因为第一步没有前一步,所以它不能合并到前一步,
所以保证第一步如果有余数,就要让distance + yushu > L,
所以distance赋初值L
当然也可以先单独处理第一步的,懒得写了
另外:
用c++的cin cout 还是会TLE,改成scanf,printf之后终于对了
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn = 200005;
int rock[maxn];
int main()
{
int t,n,m,l,casee = 1;
cin >> t;
while(t--)
{
cin >> n >> m >> l;
for(int i = 1; i <= n; ++i)
scanf("%d",&rock[i]); ///rock[0] = 0把起点也当做石头并且位置为0
sort(rock,rock+n+1);
rock[1+n] = m; ///把对岸也当做石头并且位置为m
int sum = 0,distance = l,cishu,yushu;
for(int i = 0; i <= n; ++i)
{
cishu = (rock[i+1] - rock[i]) / (1 + l);
yushu = (rock[i+1] - rock[i]) % (1 + l);
if(yushu + distance > l)
{
sum += 2 * cishu + 1;
distance = yushu;
}
else
{
sum += 2 * cishu;
distance += yushu;
}
}
printf("Case #%d: %d\n",casee++,sum);
}
return 0;
}