二分学习笔记

二分的简单介绍

度娘的定义
对于区间 \([a,b]\) 上连续不断且 \(f(a)\cdot f(b)<0\) 的函数 \(y=f(x)\),通过不断地把函数 \(f(x)\) 的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。


使用条件

根据度娘的定义,使用二分需要满足两个条件:

  1. 边界确定在一个区间 \([a,b]\)
  2. 函数在区间 \([a,b]\) 内具有单调性
    满足以上两个条件,就可以考虑进行二分

时间效率

对于一个区间长度为 \(n\) 的函数,每次范围缩小为原来的一半,时间复杂度为 \(O(\log n)\)\(\log n\),当 \(n=1e18\) (绝大多数不使用高精度题目的数据的最大上限) , \(\log n\approx 60\) (一个不到100的小常数 ),因此二分的时间效率还是挺可观的

题型

简单的二分问题一般有两种题型:整数二分和实数二分,其中整数二分考察较多。


整数二分

整数二分常用方法有二分查找和二分答案

二分查找

当题目给定一个较长的单调序列,多次求序列中某个大于 \(x\) 或大于等于 \(x\) 的值,可以考虑使用二分查找。

一般可以使用 STL 中的 upper_bound() 和 lower_bound() ,两个函数均在头文件 algorithm 中。

二分答案

二分答案的题目一般难点在于找到单调性,并设计出一个 check 函数,一般需要根据题目要求手写 check 函数

下面是代码模板。

while(l<r)
{//写法1
	int mid=l+r>>1;
	if(check(mid))
	{
		l=mid+1;
		...//维护xxx
	}
	else
	{
		r=mid;
		...//维护xxx
	}
	...//维护xxx
}

while(l<r)
{//写法2
	int mid=l+r+1>>1;
	if(check(mid))
	{
		l=mid;
		...//维护xxx
	}
	else
	{
		r=mid-1;
		...//维护xxx
	}
	...//维护xxx
}

以上两种写法,在 acwing 中有y总清晰的讲解,在此就不多谈了。

不过我本人更习惯于以下这种写法,本人认为也更适合初学者。

while(l<=r)
{
	int mid=l+r>>1;
	if(check(mid))
	{
		r=mid-1;
        ans=mid;//存储二分出的答案
		...//维护xxx
	}
	else
	{
		l=mid+1;
		...//维护xxx	
	}
	...//维护xxx
}

个人认为第三种写法比较简洁无脑,不用考虑边界问题导致死循环(注意 check 函数返回\(1\)说明 ans 在 mid 左边)。具体选择哪种写法,因人而异,只要写法正确,习惯用一个就最好不要随意更改。

对于题目单调性的判断以及 check 函数的设计,是通过做题量积累出来的经验。做的多了,也就游刃有余了。


经典例题

洛谷

P2678跳石头(二分)

P4343自动刷题机(二分)

P1902刺杀大使(二分+bfs/dfs/最小生成树+并查集判断连通性)

P1314聪明的质监员(二分+前缀和维护)
个人题解

P1083借教室(二分+差分维护)


实数二分

相比整数二分,实数二分的模板更简单,没有边界问题。需要注意精度eps的选取,一般题目会给出。

下面是代码模板

while(r-l>=eps)//这里只要eps符合题意,取不取等都可以
{
	double mid=(l+r)/2;
	if(check(mid))l=mid+eps,ans=mid;//同样ans存储二分的答案
	else r=mid-eps;
}

经典例题

P1024一元三次方程求解(整数二分)

个人题解


update

2023.5.25大体构架完成
2023.5.26增加了部分经典例题及题解
2023.7.17修缮部分内容,使文章更加科学精简,将本博客调整至文章中,在随笔中归到分治一类。

参考资料:oi wiki,acwing,百度,算法竞赛(罗勇军 郭卫斌著)

posted @ 2023-05-25 16:13  week_end  阅读(32)  评论(0编辑  收藏  举报