二分法自我总结


前言

当题目答案是一个值,且在固定范围内的话,就可以考虑二分法求解。


一、二分法的模板?

bool check(int mid){
	return ;//返回true或者false;
}
int l = ma , r = sum;
    while (l<r){
        int mid = (r + l) /2;
        if(check(mid)){//根据实际调整
            r = mid;
        }else{
            l = mid + 1;
        }
    }

二 对应题目

1.蓝桥杯【打包】

题目链接

在这里插入图片描述

显然,我们可以想到让每个人的礼物重量更接近于总体的平均值,那么就越平均,但是每个人的礼物应该比平均值多一点还是少一点,尚且不可知,但是我们可以知道这个答案的最大值,一定位于(所有礼物的重量的最大值)和(所有礼物总重之间)并尽可能让这个值小。

而且我们可以发现二分性质:如果每个人分到的礼物总重最大值小于答案,那么会出现分不完的情况,即分发礼物失败,而大于答案,会有可以成功分发礼物。

那么check()函数可以这样写

bool check(int mid){
    int now = 0;//每一包礼物的当前总重
    int p = 1 , i = 0;
    for (int i = 0; i < n; i ++ ){
        
        if(t[i]>mid)return false;//如果有某单个礼物重量超过检测值,不可能分发成功
        
        now += t[i];
        
        //如果超过检测值,表示完成一包分发
        if(now > mid){
        
            now = t[i];
            p++;
        }
    }
    //如果可以分发给更少的人,则一定可以分给更多的人
    return p <= m;
    
}

这样就可以通过二分法找到答案。
下面为完整代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;



const int N = 100010;

int t[N];
int n , m ;


bool check(int mid){
    int now = 0;
    int p = 1 , i = 0;
    for (int i = 0; i < n; i ++ ){
        
        if(t[i]>mid)return false;
        
        now += t[i];
        
        if(now > mid){
            now = t[i];
            p++;
        }
    }
    
    return p <= m;
    
}


int main()
{
    cin >> n >> m;
    int sum = 0 ;
    int ma = 0;
    for (int i = 0; i < n; i ++ ){
        scanf("%d", &t[i]);
        sum+=t[i];
        ma = max(ma , t[i]);
    }
    //如果左值设为单个礼物的最大值,check()函数中的这个检查可以不写
    int l = ma , r = sum;
    while (l<r){
        int mid = (r + l) /2;
        if(check(mid)){
            r = mid;
        }else{
            l = mid + 1;
        }
    }
    cout << l;
}

2.蓝桥杯【和谐宿舍】

题目链接
在这里插入图片描述

其实这道题和上面的【打包】很相像,都有类似的二分性质,但是其中的check()中now的计算需要做出相应的调整

bool check(int mid){
    int now = 0 , idx = 0;
    int p = 1 , i = 0;
    for (int i = 0; i < n; i ++ ){
   		//如果单个高度超过了判断的最大值,直接返回false
   		//如果主函数的l是从ma开始的,这行代码可以不加
        if(t[i]>mid)return false;
        
        //如果作为一块挡板时的高度
        now = max(now , t[i]);
        
        //面积大于检测值,即为一块挡板
        if(now * (i - idx + 1) > mid){
            idx = i;
            now = t[i];
            p++;
        }
    }
    
    return p <= m;
    
}

3.AcWing【社交距离I】

题目链接
在这里插入图片描述

这道题也有贪心的性质,即我们可以选择在最大距离处安置两头奶牛,或者在最大和第二大距离分别安置一头奶牛,需要分类讨论(这里不提)

二分方法:
首先我们可以找到答案是不会超过原先的最短距离的,而且在社交距离小于答案都可以成功放置奶牛,大于答案则无法放置两头奶牛。

check()函数中应注意奶牛队列的边界处理,如以下情形
0000000000
1000000000
0000010000

我们遍历所有的间距,并对左右做好处理。完整代码如下

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

string s;

const int N = 200010;

int n , cnt;
int dis[N] ;
bool ll , rr;


bool check(int mid){
    int sum = 0;
    for (int i = 0; i <= cnt; i ++ ){
        if(i==0 && ll|| i== cnt && rr){
        	//边界一端可以放牛
            if(dis[i] >= 2 * mid){
                sum+=2;
            }if(dis[i] >= mid){
                sum++;
            }
        }else{
            if(dis[i] >= 3 * mid - 1){
                sum+=2;
            }if(dis[i] >= 2 * mid-1){
                sum++;
            }
        }
        if(sum>=2){
            return true;
        }
    }
    return false;
}



int main()
{
    cin >>n;
    getchar();
    cin>>s;
    
    for (int i = 0; i < n; i ++ )
    {
        if(s[i]=='0'){
            dis[cnt]++;
        }else{
            cnt++;
        }
    }
    if(dis[0])ll = true;
    if(dis[cnt])rr = true;
	//如果全为0 , 直接两边各放一头
    if(!cnt){
        cout << n - 1;
        return 0;
    }
    int mi = 100010;
    for (int i = 1; i < cnt; i ++ ){
        mi = min(mi , dis[i]);
    }
	
	//此处的右端点值为中间间隔的最小值加1,算上最两边的间距可能为0
    int l = 0 , r = mi + 1;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if (check(mid))
            l=mid;
        else
            r=mid-1;
    }
    cout << l;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值