二分答案入门(两个二分模板的使用)

二分答案 ——最大值最小化问题和最小值最大化问题

本来想着每天更一篇,但是昨天由于各种原因断了…
二分答案这个知识陆陆续续看了好几天,也看了好多博主的文章,虽然明白基本二分的基本思路,但是那个check函数是真的难写,到现在还是有点迷。

看文章的时候我见到过好多二分答案模板的写法,不过还是觉得下面这两个比较舒服:
(l和r分别为初始时区间的下界和上界)
①当二分区间为[l,mid] [mid+1,r]时:

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

②当二分区间为[l,mid-1] [mid,r]时:

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

上面两个模板分别对应两种题型:最大值最小化问题和最小值最大化问题
具体为什么能解决这两类问题,这位大佬的证明和总结我觉得还是比较OK的:https://www.acwing.com/blog/content/91/
下面以两道最近做过的题为例:

1.计蒜客信息学题库 T1877 数列分段
(题目是从洛谷上截的)
在这里插入图片描述
题目很直观了,最大值最小化。我感觉这道题还有点模拟的意思在里面,模拟把一列数分成m段。由于要找的是最大值,并且数列的顺序确定了,那么根据贪心来说,加的数越多值越大。其实就是每次在比mid小的数中,找到最大的那个,然后利用check函数和上面的两个模板更新区间

直接上AC 代码了:(经过大佬指点,并且想了好久后的ac代码…不得不说数学真的太重要了)

#include <bits/stdc++.h>

using namespace std;
int m[100005];
int N,M;

bool check(int x)
{
	int sum=0;
	int cnt=0;//把cnt模拟成分割线
	for(int i=1;i<=N;i++)
	{
		sum+=m[i];//一直往右贪心
		if(sum>x)//如果比mid大,那此时后面的数就不能加上了,前面数的和就是比mid小的数中最大的
		{
			cnt++;//画一条分割线
			sum=m[i];//sum要重新开始
		}
	}
	return cnt<=M-1;//这里就是当要分三段时,需要的分割线就是两条 
					//但是当cnt小于M-1时,说明此时的分法并不是最优解,还能按照这种分法继续往下分,所以返回true
					//如果cnt>M-1 那就跟题意不一样了,直接返回false
} 

int main()
{
	cin>>N>>M;
	int l=0;
	int r=0;
	for(int i=1;i<=N;i++)
	{
		cin>>m[i];
		l=max(l,m[i]);//根据题意确定最初的区间 如果不能确定,那可以把l初始化成1,把r初始化成一个比较大的数
		r+=m[i];
	}	
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			r=mid;
		}
		else
		{
			l=mid+1;
		}
	}
	cout<<l<<endl;
	return 0;
}

2.NOIP 2015 跳石头
在这里插入图片描述
最短距离的最大值即最小值最大化,明确两个问题:移动的石头越多距离越大,相邻两个石头的距离一定是比中间隔着石头要短。同样需要把移动石头的过程模拟出来,每次在比mid大的数里找到最小的那个,然后实现区间的更新。
AC代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N=50005;

int n,m;
LL d[N];
LL L;

bool check(int x)
{
    int p=1;//起点的石头和终点的石头不能移 保留起点的石头
    int cnt=0;//记录有几个石头被干掉
    for(int i=1;i<=n;i++)
    {
        if(d[i]-p>=x)//每次和起点的石头相减 因为和第一个石头相减的值一定大于和前一个石头相减的值
        {
            p=d[i];//记下比mid大的最小那个数
        }
        else//如果不比mid大 就干掉一个石头
        {
            cnt++;
        }
    }
    return cnt<=m;
}

int main()
{
    scanf("%d%d%d",&L,&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&d[i]);
    }
    int l=0;
    int r=L;
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(check(mid))
        {
            l=mid;
        }
        else
        {
            r=mid-1;
        }
    }
    printf("%d\n",l);
    return 0;
}

不过还有个问题,第二个模板里求mid为啥还要加1

数学是真的有用,但是到大学依然很菜…

  • 11
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值