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


    题目背景

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

    题目描述

    这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 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;
    }

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

    原绿色洒满天际
  • 相关阅读:
    opencv目录
    qt5-编译并添加opencv库
    java版gRPC实战之二:服务发布和调用
    java版gRPC实战之一:用proto生成代码
    github搜索技巧小结
    client-go实战之五:DiscoveryClient
    client-go实战之四:dynamicClient
    client-go实战之三:Clientset
    client-go实战之二:RESTClient
    client-go实战之一:准备工作
  • 原文地址:https://www.cnblogs.com/sc-pyt-2021-theworld/p/10340014.html
Copyright © 2020-2023  润新知