问题描述
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1 且 N≥M≥0 。
接下来 N 行,每行一个整数,第 i 行的整数
Di(0<Di<L)
D
i
(
0
<
D
i
<
L
)
, 表示第 ii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值。
样例输入
25 5 2
2
11
14
17
21
样例输出
4
思路分析
看到最小值最大我就想到用DP,很轻松的就构造出来状态,就是dp[起始点][终点][剩余移动石头次数]代表最优解,然后类似于区间dp去做,于是就有大佬说不行!
因为这样数据范围大光dp数组的空间就是5e4 * 1e9 * 1e9 = 5e22…
所以不行
不过蒟蒻会暴力。。。
当然按这个数据暴力肯定是TLE
所以再看这个
我们可以二分答案
………以下来自大佬博客
这道题如果要使用暴力搜索直接求解会严重超时。实际上,我们可以发现,这个所谓的最短跳跃距离显然不能超过一个范围,而这个范围题目上已经给了出来。也就是说,答案是有一个确定的范围限制的,我们就可以考虑一种另外的方法去解决——枚举答案,并去验证答案是否可行。
实际上,枚举答案有时候也会超时。这就好比说你要从一本英汉词典上查一个单词,你从头到尾一页一页的翻着找,这样找可以保证一定能找到,但是最坏情况你要把整本词典都翻一遍,那就麻烦了。
有什么改进的方法吗?当然有。
考虑把这个词典从中间分开,看一下中间那一页的主要单词都是啥,然后去判断我要找的单词应该在左半部分还是右半部分,再去那一部分考虑怎么找就好了。同样的,在另一部分也是要进行划分并且判断的操作。这样一直进行下去,便能很快的找到答案,而且根本不需要翻过整个词典来。
可以证明,如果一页一页的找,最多要找n次,但是用这个方法,最多找floor(log2n)次。
我们把这个方法叫做“二分答案”。顾名思义,它用二分的方法枚举答案,并且枚举时判断这个答案是否可行。但是,二分并不是在所有情况下都是可用的,使用二分需要满足两个条件。一个是有界,一个是单调。
二分答案应该是在一个单调闭区间上进行的。也就是说,二分答案最后得到的答案应该是一个确定值,而不是像搜索那样会出现多解。二分一般用来解决最优解问题。刚才我们说单调性,那么这个单调性应该体现在哪里呢?
可以这样想,在一个区间上,有很多数,这些数可能是我们这些问题的解,换句话说,这里有很多不合法的解,也有很多合法的解。我们只考虑合法解,并称之为可行解。考虑所有可行解,我们肯定是要从这些可行解中找到一个最好的作为我们的答案, 这个答案我们称之为最优解。
最优解一定可行,但可行解不一定最优。我们假设整个序列具有单调性,且一个数x为可行解,那么一般的,所有的x’(x’
AC代码
#include <iostream>
#include <cstdio>
using namespace std;
int l,m,n,d[50050],ans;
bool jduge(int lim){
int tot=0;
int next=0;
int cur=0;
while(cur<n+1){
next++;
if(d[next]-d[cur]<lim)tot++;
else {
cur=next;
}
}
if(tot>m)return false;
return true;
}
int main(){
cin>>l>>n>>m;
for(int i=1;i<=n;i++)cin>>d[i];
d[n+1]=l;
int le=1,ri=l,mid;
while(le<=ri){
mid=(le+ri)/2;
if(jduge(mid)){
ans=mid;
le=mid+1;
}
else ri=mid-1;
}
cout<<ans<<endl;
return 0;
}