二分

整数二分法的刷题记录

关于模版

二分的模板有很多种 鹅鹅鹅
😯有一种习惯的写法 = ̄ω ̄= 下面蒟蒻来解释一下

1.求最大的最小值类型

int binary_search_1(int l, int r)
{
    while (l < r)//l,r分别是左右边界
    {
        int mid = l + r + 1 >> 1;//切记要加上1,防止死循环
        //这里是根据下面这个l=mid还是l=mid+1决定的
        //如果是l=mid 呐么就一定要加上1(下面给出解释)
        if (check(mid)) l = mid;
        else r = mid - 1;
    }//check函数其实是最重要的!!我会在题目中详细说明
    return l;
}

好了现在有个问题就是为什么在上面这个l+r要加上1,这里大家看一下,while循环到最后就是r=l+1,如果这个时候mid=(l+r+1)>>1,因为是向下取整,mid的值会变成l,但是我下面又可能会把mid赋值给l,这样就会一直在mid=l,l=mid,一直在里面死循环出不来了。

2.求最小的最大值类型

int binary_search_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;  
        else l = mid + 1;
    }
    return l;
}

这里无非就是两种类型,基本的思想:根据中间值去判断下一步是在左边寻找还是右边寻找。最主要的就是边界问题,以上的两个模版要记得才能去做题,但是这这个不能去死记,要根据题目的要求写出check函数然后才能去写这个模板。下面我借几个题目来说一下check函数怎么写和这个模板怎么用。

关于题目

洛谷P1824 进击的奶牛
洛谷P1824 进击的奶牛大意:一个人建造了一个有N个隔间的牛棚,要使得所有牛中相邻两头的最近距离越大越好。那么,求这个最大的最近距离
题目给出了N个牛棚的坐标和牛的数目。
先说一下:这种题目一般都是去二分答案,就是把这个最近的距离二分出来
可以想象一下,如果🐂距🐂离取得大了,那么能放下的牛就少了,如果🐂距🐂离取得小了,放是能放下去,但是这个距离不满足要求,感觉就是这个地方可以二分,题目中的牛的数量已经给出,就是根据这个来写check函数。
这里明显就是第一个求最大的最小值的问题。直接把上面的模板拿过来用。
我下面的题目都会用到这两个模版,感觉是万能的!但是一定要在理解的基础上面用!!!希望大家可以看懂
上代码

#include<iostream>//不喜欢用万能头的男人
#include<stdio.h>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include<stack>
#include<vector>
#include<set>
#include<map>
#define Debug(in) cout<<#in<<"="<<(in)<<endl
#define mm(a,x) memset(a,x,sizeof(a))
#define sync std::ios::sync_with_stdio(false);std::cin.tie(0)
#define endl '\n'
#define PI acos(-1.0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
int N,F;ll a[100010];
bool check(ll x)
{
    int ans=1,tmp=a[1];//ans表示的能放几头牛
    for(int i=2;i<=N;++i)
    {
        if(a[i]-tmp>=x)//如果一旦大于等于这个最小值
        {
            ans++;//那么这个地方就应该放一个牛了
            tmp=a[i];//一定要记录一下位置,那么下一次才知道上一次在哪放的牛
        }
    }
    return ans>=F;//这里的意思是能放的牛大于有的牛
    //说明距离还可以大一点
    //这里这个等于号没了就做不到答案了
}
int main()
{
    scanf("%d%d",&N,&F);ll tmp=0;

    for(int i=1;i<=N;++i)
    {
        scanf("%lld",&a[i]);//读入牛栏的坐标
        tmp=max(a[i],tmp);
    }
    sort(a+1,a+N+1);//数据不是按大小顺序输入要排序
    int l=1,r=tmp;//左边界最小为1不可能重叠,右边就粗暴的最到位置最大值
    while(l<r)//这里就是上面那个模板
    {
        ll mid=(l+r+1)>>1;//加上1莫忘记!!
        if(check(mid))
            l=mid;//这个就要根据check函数的意思来写(下面具体讲)
            //check中返回的是能放牛的数量大于了有的牛的数量
            //所以我这些牛与牛的距离就可放大一点看看就到右边去寻找看看行不行
        else
            r=mid-1;
            //哎呦!这里不满足牛的数量了,因为牛与牛的距离嫌大,
            //已经放不下我的牛了,缩小距离到左边看看
    }
    cout<<r;//其实最后l和r是一样
    return 0;//养成好习惯,不然扣分呜呜呜

}

我下面还会列一个题和上面那个差不多的题,但是check函数最后返回的东西却不一样噢!这个地方有点意思,一定要理解题目的意思。下面这个还是求最大值,我又把那个求最大模版拿出来。

P2678 NOIP2015 提高组跳石头
P2678 NOIP2015 提高组跳石头大意:起点和中点有一个岩石,中间有N个岩石,现在要移走M块岩石,问从起点调到重点,最大的最近距离。
题目给出了长度,岩石的坐标,要移走的M块岩石。

  • 依旧是上面那一题的思路,二分出最大距离。
  • 可以想象一下,如果拿走的石头多了,那么我的距离就会跨大,如果拿少了,距离就会跨小一些,题目给出了M块,就根据移走的岩石来写check函数,根据这个判断我要放大距离还是缩小。
  • 做这题之前要弄清题目意思,起点和重点不可以移走。我在程序里面就用a[0]存起点,a[n+1]存终点。这里从0-n+1一共就是n+2块岩石
    上代码
#include<iostream>
#include <iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include <stack>
#include<vector>
#define Debug(in) cout<<#in<<"="<<(in)<<endl
#define mm(a,x) memset(a,x,sizeof(a))
#define sync std::ios::sync_with_stdio(false);std::cin.tie(0)
#define endl '\n'
#define PI acos(-1.0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
ll d,n,m;string s;
ll a[100010], b[10010];bool flag;
bool check(ll x)
{
    ll i=0,tmp=0,ans=0;
    while(i<n+1)//注意i不能到重点,面累加了
    {
        i++;//这里
        if(a[i]-a[tmp]<x)//如果岩石距离小于我二分的这个距离
            ans++;//这块岩石就可以拿走
        else
            tmp=i;//否则就标记一下
    }
    return ans<=m;//这里就和上面一题完全不同了,下面会拿出来讲
}
int main()
{
    cin>>d>>n>>m;ll ans=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    a[0]=0,a[n+1]=d;//起点终点
    ll l=0,r=d;//左右边界
    while(l<r)
    {
        ll mid=(l+r+1)>>1;
        if(check(mid))//这里在上一题都有解释
            l=mid;
        else
            r=mid-1;
    }
    cout<<r;
    return 0;
}

关于check函数

其实我最想说的就是check函数,模版没有什么好说的,可能才开始学二分的时候迷迷糊糊的,感觉背个模版就行,其实一点都不对,整数二分的边界真的搞的人蛋疼,要去理解这个问题,才能真正把边界问题处理好,有的时候写对了,那也只是运气好,下面我说一下check函数怎么和这个二分模版结合,不对还请大佬指正

牛栏的这个问题,check函数里面是判断的设定的牛栏数目,也就等同于牛的数目,根据这个题目我们可以看到明显就是最大值问题,模版就出来了,check函数有两个部分,1.一个是计算数目,这个就是模拟的过程,一旦发现距离大于或等于最小的距离时,这个地方就可以设定一个牛栏了,而且要记住这个位置,以后设定的时候要减去这个坐标来判断距离。2.就是返回值(!!!)虽然就是最大值的那个模版,但是这个返回值直接决定了if 和else 后面所写的内容!!这个地方就要理解了,我要返回一个什么样子的结果,如果if(check(mid))后面跟的是l=mid,那么就说明我现在是往右边区间找,就说明我刚刚这个牛栏的距离嫌小,那么是什么原因导致牛栏的距离小的呢?就是肯定我选的牛栏多了,那么牛栏牛栏的距离才会小,那么check里面最后返回的就是return ans>=F 就是我选的牛栏大于了我所有的牛,进一步我才可以去扩大,注意这个里面是根据if后面的语句去反过来推return什么的,当得到了这个后还可以正过来推也完美符合题意。
取岩石这个题,我再这样分析一下,1.模拟,算出现在在二分的这个距离下我需要拿走多少块岩石,就是如果岩石之间的距离小于这个最小距离,我就把这块岩石拿走,计数加一,并且记录下位置,方便记录下一个岩石与这个之间的距离。2. check()函数,我现在要返回怎么样一个值呢?看if后面是什么,入果是l=mid,那么就说明我现在要把这个跨的距离取大点,说明之前的距离嫌小,说明拿走的岩石数量不够我所规定的岩石数目,所以,return ans<=m,那么我再正过来推,由于现在我取的岩石少了,所以我就要把距离取的大一点,跨度大了,那么拿走的岩石就多了。

注意:这里求的最大值 ,如果二分语句if后面是l=mid,那么就是包含了中点在内的右边区间都有可能是答案,在check函数里面判断好是大于还是小于的时候一定要加上等于号,如果不加那就完完。
分析的有点长,不过却最精华的部分(~ ̄(OO) ̄)ブ

再来一道和第一题一样的题目

POJ 2456 Aggressive cows
这里代码我稍微变动一点点 就是把二分的那个if else 语句换一下,那check函数里面那个return也跟着换一下就行
ο(=•ω<=)ρ⌒☆
注释我就不写了 嘿嘿嘿
上代码

#include<iostream>
#include<stdio.h>
#include <iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include <stack>
#include<vector>
#include <set>
#include <map>
#define Debug(in) cout<<#in<<"="<<(in)<<endl
#define mm(a,x) memset(a,x,sizeof(a))
#define sync std::ios::sync_with_stdio(false);std::cin.tie(0)
#define endl '\n'
#define PI acos(-1.0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
int N,F;ll a[100010];bool flag;
bool check(ll x)
{
    ll cnt=1,tmp=a[1];
    for(int i=2;i<=N;i++)
    {
        if(a[i]-tmp>=x)
        {
            cnt++;
            tmp=a[i];
        }
    }
    return cnt<F;//就是这里变了一下下
}
int main(void)
{
    scanf("%d%d",&N,&F);ll sum=0;
    for(int i=1;i<=N;i++)
    {
        scanf("%lld",&a[i]);
        sum+=a[i];
    }
    sort(a+1,a+N+1);
    ll l=0,r=sum;
    while(l<r)
    {
        ll mid=(l+r+1)>>1;
        if(check(mid))
           r=mid-1;//这里交换了一下
        else
           l=mid;//
    }
    cout<<l;
    return 0;
}

其实搞懂了都能做的
下面再来一个最小值的题目
{ { x → a d f − a 2 b c 2 d 2 e 2 + a b 2 c 2 e 4 − a b e 2 f 2 a d 2 + b e 2 , y → d − a b e 2 ( − a c 2 d 2 − b c 2 e 2 + f 2 ) a d 2 + b e 2 − a d 2 f a d 2 + b e 2 + f e } , { x → a 2 b c 2 d 2 e 2 + a b 2 c 2 e 4 − a b e 2 f 2 + a d f a d 2 + b e 2 , y → − d − a b e 2 ( − a c 2 d 2 − b c 2 e 2 + f 2 ) a d 2 + b e 2 − a d 2 f a d 2 + b e 2 + f e } } \left\{\left\{x\to \frac{a d f-\sqrt{a^2 b c^2 d^2 e^2+a b^2 c^2 e^4-a b e^2 f^2}}{a d^2+b e^2},y\to \frac{\frac{d \sqrt{-a b e^2 \left(-a c^2 d^2-b c^2 e^2+f^2\right)}}{a d^2+b e^2}-\frac{a d^2 f}{a d^2+b e^2}+f}{e}\right\},\left\{x\to \frac{\sqrt{a^2 b c^2 d^2 e^2+a b^2 c^2 e^4-a b e^2 f^2}+a d f}{a d^2+b e^2},y\to \frac{-\frac{d \sqrt{-a b e^2 \left(-a c^2 d^2-b c^2 e^2+f^2\right)}}{a d^2+b e^2}-\frac{a d^2 f}{a d^2+b e^2}+f}{e}\right\}\right\} xad2+be2adfa2bc2d2e2+ab2c2e4abe2f2 ,yead2+be2dabe2(ac2d2bc2e2+f2) ad2+be2ad2f+f,xad2+be2a2bc2d2e2+ab2c2e4abe2f2 +adf,yead2+be2dabe2(ac2d2bc2e2+f2) ad2+be2ad2f+f
模版就用第二个,check函数和二分还是那样分析就行

POJ 3273 Monthly Expense
POJ 3273 题意:给出N天,将N分成连续的M段,求分成M段之后的每段的最大的最小值。
上代码

#include<iostream>
#include<stdio.h>
#include <iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<string>
#include <stack>
#include<vector>
#include <set>
#include <map>
#define Debug(in) cout<<#in<<"="<<(in)<<endl
#define mm(a,x) memset(a,x,sizeof(a))
#define sync std::ios::sync_with_stdio(false);std::cin.tie(0)
#define endl '\n'
#define PI acos(-1.0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
int N,F;int a[100010];bool flag;
bool check(ll x)
{
    int cnt=1;ll sum=0;
    for(int i=1;i<=N;i++)
    {
        sum+=a[i];
        if(sum>x)
        {
            cnt++;
            sum=a[i];
        }
    }
    return cnt<=F;//这个地方注意
}
int main(void)
{
    scanf("%d%d",&N,&F);ll tmp=0,sum=0;
    for(int i=1;i<=N;i++)
    {
        scanf("%d",&a[i]);
        tmp=max(tmp,(ll)a[i]);
        sum+=a[i];
    }
    ll l=tmp,r=sum;
    while(l<r) {
        ll mid=(l+r)>>1;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    cout<<r;
    return 0;
}

辣么就结束么 木有!更多待续。。。(未完)点赞点赞

致像我一样的蒟蒻

转载请注明出处wyzhf99

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值