[P2678 [NOIP2015 提高组] 跳石头](P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
先来简单点个题:
此题所求的是石头与石头最短距离的最大值,看到此处,熟悉的小伙伴就应该知到这是二分答案的典型标志了。但针对不熟悉的,也别急,我来告诉你为什么会是用到二分答案。一般用上二分答案的例题所给数据都有一定的特点,第一就是在有限范围内,石头与石头之间的距离,最小的距离就是两个石头挨着,最大距离也就是起点到终点的距离。如此一看,也就能看出是在有限范围内找最大值。第二就是直接搜索很麻烦且工作量巨大,但特别容易判断一个答案是否可行。第三在这个有限区间内对于题目具有单调性,就拿此题来说,最小距离越大,搬的石头越多。
说了这么多,这题该如何去做呢?
在有限区间内,最短距离不唯一,而最大的最短距离是唯一的,此时我们需要找到区间内满足小于等于搬走石头的最大数m的最大的最短距离。用二分查找找到最大的一个,根据二分查找性质可以知道,此时mid=l+r+1/2。而根据条件便是最大能搬走的石头的数量m,写出判断该往左找还是往右找的check函数。而此题的关键就在于check函数如何写?
此时二分查找到的mid是我们现在拟定的最大的最短距离,此时mid可能是我们所要找的,但也可能不是,我们要以限定条件最大搬石头的数量m来决定是与不是。从除起点的第一个点开始,其距离下一个点的距离是否小于此时拟定的最大的最短距离,如果小于,那么就得搬走下一个石头,再以这个点进行下一个石头,若还小继续搬走,以此类推直到大于最小距离就以下一个石头为起点算与接下来的各个点的距离再依据mid确定搬还是不搬。最终比较实际的搬石头数与要求给的最大限定的搬石数m,如果实际大于m,那么就说明mid大了,反之则满足。
代码如下:
#include<iostream>
using namespace std;
const int N=50010;
int a[N],n,len,m,mina=1e9+1,b[N];
int check(int mid) //检查,是否最短距离为mid,如果两石头间距小于mid,不满足,移走
{
int cnt=0;
int i=0,last=a[0]; //i表示目标位置,last为当前位置。
while(i<n+1){
i++;
if(a[i]-last<mid){ //两石头间距离小于mid,mid不是最短距离,不满足,移走该石头
cnt++;
}
else{ //符合,跳过去
last=a[i];
}
}
if(cnt<=m) return 1; //移走的石头个数小于 M,就能保证了任意两剩下的石头间距大于等于最短距离mid,那移走M个,更能保证
return 0;
}
int main(){
cin>>len>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]<mina) mina=a[i];
}
a[0]=0,a[n+1]=len; //首尾都有石头
if(n==0){ //起点和终点之间没有石头的情况。
cout<<len; return 0;
}
//检查每一个mid是否符合要求
long long l=1,r=mina;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid; //要的是距离的最大,所以尽可能地往右走
else r=mid-1;
}
cout<<l;
return 0;
}