简单的介绍整数二分

写在前面

首先什么是二分,比如我们在一个序列找一个数时,在这个序列有序的前提下,我们先看中间的数与要找的目标值的大小,根据大小再在左边或者右边找,然后再取剩下区间的中间值,这样可以很快找到目标值,这就是狭义理解上的二分,但这不能说明二分的本质;

二分的本质

二分

是的,二分的本质就是找边界的,至于什么二分查找,二分答案什么的,名字花里胡哨的,但他们的实质都是找边界,所以没有必要分要把他们分开,也不必把他们理解的多么高深,在数电课上,我们老师在讲题目时,曾多次引用这样一句话“一切反动派都是纸老虎!”,所以啊,没什么的,多写点题就会了,哎对,数电老师说得多练,多练练就会了!

二分模板

为什么会有二分模板,那是因为我们写的时候,常常因为某些边界导致死循环,所以,就有人总结了整数二分的一般模板,按照他给的框架,这样不会出错;·
来自:https://www.acwing.com/blog/content/277/

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

注:mid=l+r>>1就是mid=(l+r)/2;
那么这两个模板什么时候用呢,何种情况下用哪个模板,我觉得这是没必要严格区分的,因为第二个模板是为了避免出现死循环,所以在mid=l+r+1>>1加了一个1,那么我们只需要在实际问题中,处理这个边界时,如果出现某一项是l=mid-1或者r=mid-1,反正出现了-1,减一的话,那么我们就在mid=l+r>>1,中加一个1,即有减必有加就可以了,其他的就没什么需要去记的了;

咱们浅看几个例题吧:

题1:

链接:p2440木材加工
建议点击链接看题目;

题目描述

木材厂有 n根原木,现在想把这些木头切割成 k段长度均为 l的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l的最大值。

木头长度的单位是 cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 和 21,要求切割成等长的 6 段,很明显能切割出来的小段木头长度最长为 5。

输入格式

第一行是两个正整数 n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n 行,每行一个正整数 Li,表示一根原木的长度。

输出格式

仅一行,即 l 的最大值。

如果连1cm 长的小段都切不出来,输出 0。

输入输出样例
输入 #1复制
3 7
232
124
456
输出 #1复制
114

分析

我们有几根木头,我们要把这些木头切割成所需要的段数,肯定是希望在满足所需要的段数前提下,想想能不能使每段的长度尽可能的长,那么二分找出这个边界;
1,我们定义的check函数就是统计如果每段切割成当前的长度可以切成几段;
2,与目标段数相比,如果没有目标段数多,那么我们就让每段剪短一点,如果等于目标段数的,那么我们尝试再剪长一点看行不行,如果大于目标段数我们就剪长一点,这便是二分了;
代码:

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+100;
int q[N];
int n,m;
//check函数看每次可以切割多少段
int check(int mid)
{
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum += q[i]/mid;
	}
	return sum;//返回切割的数量
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&q[i]);
	sort(q+1,q+n+1);//排序
	if(q[n]<1) printf("%d\n",0);//一段都切不成返回0
	else
	{
		int l=0,r=q[n];
		while(l<r)
		{
			int mid = (l+r+1)/2;//因为下面有mid-1,所以这里要加1,避免死循环
			if(check(mid)>=m) l=mid;//这就是切割的段数大于等于目标段数,那么我们尝试再切割长一点,即在右边找
			else r=mid-1;//小于目标段数就切割短一点
		}
		printf("%d\n",l);
	}
	return 0;
}
题2:

链接:p4135月度开销
建议点击链接看题目;

描述

农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。

约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。

约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。

输入

第一行包含两个整数N,M,用单个空格隔开。
接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。

输出

一个整数,即最大月度开销的最小值。
样例输入

7 5
100
400
300
100
500
101
400

样例输出

500
分析

这题也被称为二分答案例1也是,不过不要管它,都是纸老虎;
1,我第一次读完题目之后我是没看懂这题目讲的啥,当时还不怎么了解二分,只是老师上课讲到了这道题,然后搜了一下,看半天没看懂这什么意思,感觉被讲的好懵,那么再读一次,简单理解就是,这个人他没钱了,所以要计划接下来的支出,然后给出了他若干天的支出,将这若干天分成一定的组数(也就是这里的M个财政周期(fajo月)),然后希望这几组最大的数尽可能的小;
2,这题与上一题感觉是差不多的,上一题是让在达到目标段数的前提让每段尽可能的长,这里就可以认为,在达到目标组数的前提让所有组最大的数尽可能的小,但这里要注意区别·,下面会解释;
3,check函数就可以定义成对于当前给的数可以分成几组(即几个周期);
4,如果比目标周期M大,说明我们每组钱数少了要增加,如果等于目标组数我们尝试能不能再减少一点点钱数,毕竟钱花得越少越好,如果小于目标周期,说明我们每组钱数多了,那么我们就要减少钱数以满足需求;
代码实现:

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+100;
int q[N];
int n,m;
//check函数统计分成几个周期
int check(int mid)
{
	int cnt=1;
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum += q[i];
		if(sum>mid)
		{
			cnt++;
			sum = q[i];
		}
	}
	return cnt;
}
int main()
{
	int l=0,r=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&q[i]);
		l = max(l,q[i]);
		r += q[i];
	}
	while(l<r)
	{
		int mid=(l+r)/2;
		if(check(mid)<=m)	r=mid;//小于等于目标周期M,则少用钱
		else	l=mid+1;//大于M,则增加钱数
	}
	printf("%d",l);
	return 0;
}

总结:
1,二分需要多练,然后你就会摸清大概什么样的题可以用二分来写,并且也知道一般写题的套路;
2,二分最难的是check函数,有的题目check函数是不容易写出来的;
3,要注意分析边界,这题的例2,首先你要分析为什么会小于M,那是因为我们每组最大的钱数给多了,那么我们减少,为什么会大于M,那是因为我们每组钱数少了,导致每组分的钱数少,所以组数增加,那么我们要增加钱数,诸如此类,我们要仔细分析,小心一点;
4,多练习,你就会了!

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值