二分学习笔记
二分的简单介绍
度娘的定义:
对于区间 \([a,b]\) 上连续不断且 \(f(a)\cdot f(b)<0\) 的函数 \(y=f(x)\),通过不断地把函数 \(f(x)\) 的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法叫二分法。
使用条件
根据度娘的定义,使用二分需要满足两个条件:
- 边界确定在一个区间 \([a,b]\) 中
- 函数在区间 \([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 函数的设计,是通过做题量积累出来的经验。做的多了,也就游刃有余了。
经典例题
洛谷
P1902刺杀大使(二分+bfs/dfs/最小生成树+并查集判断连通性)
实数二分
相比整数二分,实数二分的模板更简单,没有边界问题。需要注意精度eps的选取,一般题目会给出。
下面是代码模板
while(r-l>=eps)//这里只要eps符合题意,取不取等都可以
{
double mid=(l+r)/2;
if(check(mid))l=mid+eps,ans=mid;//同样ans存储二分的答案
else r=mid-eps;
}
经典例题
update
2023.5.25大体构架完成
2023.5.26增加了部分经典例题及题解
2023.7.17修缮部分内容,使文章更加科学精简,将本博客调整至文章中,在随笔中归到分治一类。
参考资料:oi wiki,acwing,百度,算法竞赛(罗勇军 郭卫斌著)