目录
声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
一、二分
0.二分法
二分是一个非常常见但是想要精通却很有讲究的一个垃圾 算法,二分的基础用法是在单调序列或单调函数中进行查找(二分查找),因此当问题的答案具有单调性,就可以通过二分把求解答案的问题转换成二分查找答案并判定答案的正确性(二分答案)。我们还可以用三分法去求解单峰函数的极值。
相信基础的二分大家都会,但是写出来二分不代表能A,因为二分的细节尤其重要,对于整数域上的二分,要注意终止边界和左右区间的取舍时的开闭情况,避免溜掉答案或造成死循环。对于实数域上的二分,要注意精度的问题。(我甚至在学习这一节之前,写的二分都是看运气,随缘调试,随缘AC,根本都没有注意到有这么多细节 )
1.整数域上的二分
本文中的二分保证最终答案处于闭区间 [ l , r ] [l,r] [l,r] 以内,并以 l = r l=r l=r 作为循环的结束条件
在单调递增序列a中查找>=x的数中最小的一个(即x或x的后继)
while (l < r) {
int mid = (l + r) / 2;
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
在单调递增序列a中查找<=x的数中最大的一个(即x或x的前驱)
while (l < r) {
int mid = (l + r + 1) / 2;
if (a[mid] <= x) l = mid;
else r = mid - 1;
}
作者给出的一套二分的流程:
- 分析问题,确定左右半段哪一个是可行区间,以及 m i d mid mid 归属哪一半段
- 根据分析结果,选择
r=mid,l=mid+1,mid=(l+r)>>1
和l=mid,r=dmi-1,mid=(l+r+1)>>1
- 二分终止条件是
l==r
,该值就是答案所在的位置。
2.实数域上的二分
实数域二分,设置eps法
一般 e p s = 1 0 − ( k + 2 ) eps=10^{-(k+2)} eps=10−(k+2)(k是需要保留的位数)
while (l + eps < r) {
double mid = (l + r) / 2;
if (calc(mid)) r = mid;
else l = mid;
}
实数域二分,规定循环次数法
for (int i = 0; i < 100; i++) {
double mid = (l + r) / 2;
if (calc(mid)) r = mid;
else l = mid;
}
二、三分
0.三分求单峰函数极值
有一类函数称为单峰函数,它们拥有唯一的极大值点,在极大值点左侧严格单调上升,在极大值点右侧严格单调下降;或者拥有唯一的极小值点,在极小值点左侧严格单调下降,在极小值点右侧严格单调上升。
为了避免混淆,我们称后一种函数为单谷函数,对于单峰或单谷函数,可以用三分求其极值。
以单峰函数 f ( ) f() f()为例,我们在函数的定义域 [ l , r ] [l,r] [l,r]上人去两个点 l m i d lmid lmid和 r m i d rmid rmid,其中 l < l i m d < r m i d < r l<limd<rmid<r l<limd<