从摆石头和牛舍问题看二分

题目背景

一年一度的“跳石头”比赛又要开始了!

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 NNN 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 MMM 块岩石(不能移走起点和终点的岩石)。

输入输出格式

输入格式:

 

第一行包含三个整数 L,N,ML,N,ML,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1L \geq 1L1 且 N≥M≥0N \geq M \geq 0NM0。

接下来 NNN 行,每行一个整数,第 iii 行的整数 Di(0<Di<L)D_i( 0 < D_i < L)Di(0<Di<L), 表示第 iii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

 

输出格式:

 

一个整数,即最短跳跃距离的最大值。

输入输出样例

输入样例#1: 复制
25 5 2 
2
11
14
17 
21
输出样例#1: 复制
4

说明

输入输出样例 1 说明:将与起点距离为 222和 141414 的两个岩石移走后,最短的跳跃距离为 444(从与起点距离 171717 的岩石跳到距离 212121 的岩石,或者从距离 212121 的岩石跳到终点)。

另:对于 20%20\%20%的数据,0≤M≤N≤100 ≤ M ≤ N ≤ 100MN10。

对于50%50\%50%的数据,0≤M≤N≤1000 ≤ M ≤ N ≤ 1000MN100。

对于 100%100\%100%的数据,0≤M≤N≤50,000,1≤L≤1,000,000,0000 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,0000MN50,000,1L1,000,000,000

附上0分代码

#include <bits/stdc++.h>
#define for(i,l,r) for(int i=l;i<=r;i++)
#define inf 0x7f7f
using namespace std;
long long ll,n,m,a[inf],mid,ans,l,r;
//首先是可行,其次考虑最优
bool check(int x){
    long  stone=0;
    long long last;
    for(i,1,n+1){
    if(a[i]-a[last]<x) stone++;
    else
    {
        last=i;
    }
}
return stone<=m?1:0;
}
int main(){
cin>>ll>>n>>m;
for(i,1,n){
    cin>>a[i];
}
a[n+1]=ll;
 l=1,r=ll;
while(l<=r){
    mid=(l+r)/2;
    if(check(mid)) {
        l=mid+1;
        ans=mid;
    }
    else r=mid-1;
}
cout<<ans;
return 0;
}

附上满分代码

#include <bits/stdc++.h>
#define for(i,l,r) for(int i=l;i<=r;i++)
#define inf 0x7f7f7f
using namespace std;
long long ll,n,m,a[inf],mid,ans,l,r;
//首先是可行,其次考虑最优
bool check(int x){
 long  stone=0;
 long long last=0;
 for(i,1,n+1){
 if(a[i]-a[last]<x) stone++;
 else
 {
  last=i;
 }
}
return stone<=m?1:0;
}
int main(){
cin>>ll>>n>>m;
for(i,1,n){
 cin>>a[i];
}
a[n+1]=ll;
 l=1,r=ll;
while(l<=r){
 mid=(l+r)/2;
 if(check(mid)) {
  l=mid+1;
  ans=mid;
 }
 else r=mid-1;//这些可以根据自己理解写
}
cout<<ans;
return 0;
}

看看有什么不同

是不是很坑

好了回归主题

二分就是一个从可行性问题到最优性问题

关键是看她可行解是在什么范围,可行解向最优解过度是左移还是右移

二分答案应该是在一个单调闭区间上进行的。也就是说,二分答案最后得到的答案应该是一个确定值,而不是像搜索那样会出现多解。二分一般用来解决最优解问题。刚才我们说单调性,那么这个单调性应该体现在哪里呢?

可以这样想,在一个区间上,有很多数,这些数可能是我们这些问题的解,换句话说,这里有很多不合法的解,也有很多合法的解。我们只考虑合法解,并称之为可行解。考虑所有可行解,我们肯定是要从这些可行解中找到一个最好的作为我们的答案, 这个答案我们称之为最优解。

最优解一定可行,但可行解不一定最优。我们假设整个序列具有单调性,且一个数x为可行解,那么一般的,所有的x'(x'<x)都是可行解。并且,如果有一个数y是非法解,那么一般的,所有的y'(y'>y)都是非法解。

那么什么时候适用二分答案呢?注意到题面:使得选手们在比赛过程中的最短跳跃距离尽可能长。如果题目规定了有“最大值最小”或者“最小值最大”的东西,那么这个东西应该就满足二分答案的有界性(显然)和单调性(能看出来)。

那就好办了。我们二分跳跃距离,然后把这个跳跃距离“认为”是最短的跳跃距离,然后去以这个距离为标准移石头。使用一个judge判断这个解是不是可行解。如果这个解是可行解,那么有可能会有比这更优的解,那么我们就去它的右边二分。为什么去右边?答案是,这个区间是递增的 ,而我们求的是最短跳跃距离的最大值,显然再右边的值肯定比左边大,那么我们就有可能找到比这更优的解,直到找不到,那么最后找到的解就有理由认为是区间内最优解。反过来,如果二分到的这个解是一个非法解,我们就不可能再去右边找了。因为性质,右边的解一定全都是非法解。那么我们就应该去左边找解。整个过程看起来很像递归,实际上,这个过程可以递归写, 也可以写成非递归形式,我个人比较喜欢使用非递归形式。

下一个问题,这个judge怎么实现呢?judge函数每个题有每个题的写法,但大体上的思想应该都是一样的——想办法检测这个解是不是合法。拿这个题来说,我们去判断如果以这个距离为最短跳跃距离需要移走多少块石头,先不必考虑限制移走多少块,等全部拿完再把拿走的数量和限制进行比对,如果超出限制,那么这就是一个非法解,反之就是一个合法解,很好理解吧。

可以去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,那这就是一个不合法的石头,应该移走。为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到i = n+1,为啥是n+1?河中间有n块石头,显然终点在n+1处。(这里千万要注意不要把n认为是终点,实际上从n还要跳一步才能到终点)。

模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。如果超了就返回false,不超就返回true。//以上皆为转载

牛舍

#include <bits/stdc++.h>
#define for(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
int n,m,a[101010],r,l,mid;
bool check(int x){
 int cow=1;
 int rgt=a[1]+x;
 for(i,2,n) {
 if(a[i]<=rgt) continue;
 else
  rgt=a[i]+x;
  ++cow;
 }
 return cow==m?1:0;
}
int main(){
cin>>n>>m;
for(i,1,n){
 cin>>a[i];
}
sort(a+1,a+n+1);
l=0,r=a[n]-a[1];
do{
 mid=(r+l)>>1;
 if(check(mid)) {
  l=mid+1;
 }
 else
  r=mid-1;
}while(l<=r);
cout<<mid;
return 0;
}

P1316 丢瓶盖

陶陶是个贪玩的孩子,他在地上丢了A个瓶盖,为了简化问题,我们可以当作这A个瓶盖丢在一条直线上,现在他想从这些瓶盖里找出B个,使得距离最近的2个距离最大,他想知道,最大可以到多少呢?

输入输出格式

输入格式:

 

第一行,两个整数,A,B。(B<=A<=100000)

第二行,A个整数,分别为这A个瓶盖坐标。

 

输出格式:

 

仅一个整数,为所求答案。

 

输入输出样例

输入样例#1: 复制
5 3
1 2 3 4 5
输出样例#1: 复制
2

说明

限时3秒

0分代码

#include <bits/stdc++.h>
#define for(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
long long n,m,a[101010],r,l,mid,ans,ct=1,now=1;
bool check(int x){
 ct=1;
 now=1;
 for(i,2,n) {
 if(a[i]-a[now]>=x)
  ++ct;
  now=i;
  
  
 }
 return ct>=m?1:0;
}
int main(){
cin>>n>>m;
for(i,1,n){
 cin>>a[i];
}
sort(a+1,a+n+1);
l=0,r=a[n]-a[1];
while(l<=r){
 mid=(r+l)/2;
 if(check(mid)) {
  ans=max(ans,mid);
  l=mid+1;
 }
 else
  r=mid-1;
}
cout<<ans;
return 0;
}

100分代码

#include <bits/stdc++.h>
#define for(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
long long n,m,a[101010],r,l,mid,ans,ct=1,now=1;
bool check(int x){
 ct=1;
 now=1;
 for(i,2,n) {
 if(a[i]-a[now]>=x){
  ++ct;
  now=i;
   }
  
 }
 return ct>=m?1:0;
}
int main(){
cin>>n>>m;
for(i,1,n){
 cin>>a[i];
}
sort(a+1,a+n+1);
l=0,r=a[n]-a[1];
while(l<=r){
 mid=(r+l)/2;
 if(check(mid)) {
  ans=max(ans,mid);
  l=mid+1;
 }
 else
  r=mid-1;
}
cout<<ans;
return 0;
}

真的就是一个括号让我调了半个小时啊啊,差点就放弃了

posted @ 2019-01-30 22:13  spytc  阅读(222)  评论(0编辑  收藏  举报