原题链接:跳石头
二分答案:个人理解就是将答案的可能区间写出,在用二分查找去寻找满足题意的答案
以跳石头为例,题目叫我们取最短跳跃距离的最大值,这个最短跳跃距离的可能性有哪些呢?这个距离的范围是[1,L/(n-m+1)],这个右边界是怎么来的?
因为这个区间最开始只有[1,L]这一个范围,每加一个石头就多一个范围,每去一个石头就少一个范围,假设石头分布均匀那就是L/(n-m+1),如果不均匀,范围就有2种情况,比L/(n-m+1)大,不是最短距离,比L/(n-m+1)小,是最短距离,所以不存在比L/(n-m+1)更大的最短距离.当然在写二分的过程不用考虑这么多,直接将L作为右边界即可,因为不满足会被二分掉,只是时间会长一点.(我这里测出来,只快了2ms,主要二分本来就o(logn)
int i=0,j=l/(n-m+1);
while(i+1!=j){
int mid=i+j>>1;
if(check(mid)) i=mid;
else j=mid;
}
Ok,我们找到了范围,这里mid就是我们假设的答案,我们要去判断这个mid是不是真正的答案,此题条件就是最多取石头数,那怎么和mid联系起来呢?
|____|____|____|____|____|____|,这是一个石头均匀的图,每段长度为mid,但一般情况石头是随机分布的,石头有可能在左边和右边.
在左边说明这个石头比我们最短距离还短,换句话说就是这个石头应该被去掉.
在右边说明这个石头比我们最短距离长,不用管它,直接去看下一个石头.
int check(int x){
int res=0,now=0;//res记录取的石头数目,now记录现在的位置
for(int i=1;i<=n+1;i++){
if(a[i]-a[now]<x) res++;//在左边,去掉,res++;
else now=i;//在右边,直接跳过,去判断这个石头和下一个石头的距离是否满足小于x
}
return res<=m;
//如果假设的答案去掉的石头小于等于真正要去掉的石头说明,最短距离小了,要变大
//反之最短距离大了,要变小
}
这里有个细节,循环的结束是n+1,为什么呢?因为最后还有一个边界L.
#include <iostream>
using namespace std;
int l,n,m;
const int N=5e4+10;
int a[N];
int check(int x){
int res=0,now=0;
for(int i=1;i<=n+1;i++){
if(a[i]-a[now]<x) res++;
else now=i;
}
return res<=m;
}
int main(){
scanf("%d%d%d",&l,&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
a[n+1]=l;//右边界
int i=0,j=l+1;
while(i+1!=j){
int mid=i+j>>1;
if(check(mid)) i=mid;
else j=mid;
}
printf("%d",i);
return 0;
}