二分法
二分法的概念
二分法(Bisection method),即一分为二的的方法。对于在区间$[a,b]$上连续不断且满足$f(a)*f(b)<0$的函数$y=f(x)$,通过不断地把函数$f(x)$的零点所在区间二等分,使区间两个端点逐步逼近零点,进而得到零点的近似值的方法。
简单来说就是
一般用于求解一个具有单调性的函数的分界点。
二分实际上也是一种枚举,只不过是使用的数学方法大大减少了无意义的枚举次数,时间复杂度也优化成$O(logn)$。
我们一般所说的二分是二分查找法,本文就用二分简说。
简单来说二分搜索法可以用来查找满足某种条件的最大(最小)的值。
单调性
在二分前,我们需要研究问题的单调性。
比如我们要求在正整数定义域内函数满足:
$$f(x) = x^2 ≥ k$$
的一个最小的x,其中k一般是一个定值。
我们很容易发现$x^2$的函数在正整数定义域内是单调递增的。
当然二分不止用在这么简单的场景,其他场景需要你自行分析。
二段性
集合中的元素有存在分界线,给定条件可以将集合中元素分为两部分,一部分满足条件,一部分不满足条件。
例如当我们要查找一个数组中小于$14$的最大的一个数:
$$1 ,3 ,8 ,13 ,15 ,20 ,23$$
这时可以发现从13和15中间就有一条分界线,左边是满足我们的条件的,右边是不满足的。
于是就可以通过这个将整个区间分为两个区间。具体分为哪段到哪段就看你的写法。
所以我们就可以确定二分的方式,以及代码编写的选择,当然,如果是实数二分就不存在这样的区别。
枚举区间
当我们使用二分的时候,还需要考虑的一点是,枚举的一个区间$[l,r]$。
这个区间一定是包含我们的最终答案的。
判断函数
1 |
|
就刚刚的例子而言,我们的判断函数$check()$即为$mid^2 ≥ k$ (mid即为二分时的中间点)
判断函数的编写也是二分的重中之重,稍微一步写错就有可能造成找不到答案或者死循环的情况。
确定答案
和题意结合,输出对应的答案,这一步尤其要注意,要理解清楚题目的意思,比如第一个大于等于的,最后一个小于的,最后一个小于等于的等等等等。需要自己多加小心(通常这些字眼就暗示了这一道题需要用二分来优化)
分区间
对应上文的分界线前后的元素,是属于整数二分的内容。
1 |
|
1 |
|
实数二分
实数二分就不用考虑整数二分的困扰,直接二分即可,要考虑精度,可以用浮点数比较法,还有一种方法是限制二分次数。
1 |
|
库函数
除了手写二分,还有一些库函数可以直接使用。
- lower_bound(x):返回大于等于 x 的第一个迭代器;
- upper_bound(x):返回大于 x 的第一个迭代器;
- binary_search(x):在容器中二分查找 x 是否存在。
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound( begin,end,num,greater
upper_bound( begin,end,num,greater
binary_search (begin,end,num,sort_rule);
意思是,begin和end-1 的查找区间内,查找“等于”num的元素,返回值为1 (true 找到) 或0 (false 没找到)。
三分法
三分的一大运用便是求解函数的极值点,虽然很高大上但是实际写起来并不复杂,基本与二分一致。
整数凸函数
1 |
|
整数凹函数
1 |
|
实数
这里采用的是限制循环次数法。
凸
1 |
|
凹
1 |
|
题目练习
P1873 [COCI 2011/2012 #5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)