一、算法内容
1.简介
二分法是一种基础但非常精妙的算法,经常能为我们打开解题的思路,也常常作为题目的其中一个重要环节出现。二分的基本用法就是在一个单调序列或单调函数中进行参照点(中心点)的移动。通过不断尝试并每次缩小一半的区间或者取值范围,来最终逼近我们的答案。根据参照点(中心点)的意义,我们分为二分查找和二分答案两大内容。二分答案常常与最大值最小,最小值最大有关系。
二分法还可以衍生出三分法,不过其适用范围很小。如果需要求出单峰函数的极值点,通常使用二分法衍生出的三分法求单峰函数的极值点。也可以先对函数求导再进行二分,但是一方面求导需要较强的数学知识,另一方面有很多单峰函数很难进行求导,所以三分法是一个很不错的解决方法。
2.模板
ll l=1,r=n,ans=-1;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
模板中l=mid+1
和r=mid+1
都可能根据答案的实际需要进行修改,它们从属于哪一个if
条件判断也需要具体情况具体分析。
二、实例分析
1.二分查找——最简单的例子
(1)背景
假设我们拥有一个长度为 n n n 的升序排列的数组,现在题目要求我们在数组中查找某一个数,并期望我们能在 O ( log n ) O(\log n) O(logn) 的时间内完成查找。下面,我们将结合这个例子具体讲解二分查找的使用方式。
(2)分析
二分查找的具体过程是:它每次将会考察我们选择的参照点(中心点)来进行下一步的判断。而在本题中,参照点就是我们这个升序数组当前部分的中间元素。
- 如果中间元素刚好是要找的,就结束搜索过程
- 如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找
- 如果中间元素大于所查找的值,那么右侧的指挥更大,不会有所查找的元素,只需到左侧查找
因为在二分搜索过程中,算法每次都把查询的区间减半,所以对于一个长度为 n n n 的数组,至多会进行 log n \log n logn 次查找。
ll l=1,r=n,ans=-1;
while(l<=r)
{
ll mid=(l+r)>>1;
if(a[mid]==aim)
{
ans=mid;
break;
}
else if(a[mid]<aim)
l=mid+1;
else
r=mid-1;
}
2.二分答案——P1873
(1)题目大意
给出 N N N 棵树,以及它们的高度序列 h h h。我们获取木材的方式是,选择一个高度 H H H,所有 h [ i ] h[i] h[i] 高于 H H H 的部分都将被砍掉并变成木材。题目要求我们能从这 n n n 棵树中获取至少 M M M 的木材,并使得 H H H 尽可能大 。
数据范围: 1 ≤ N ≤ 1 0 6 , 1 ≤ M ≤ 1 0 9 , h [ i ] < 1 0 9 , ∑ h [ i ] > M 1\leq N\leq10^6,1\leq M\leq10^9,h[i]<10^9,\sum h[i]>M 1≤N≤106,1≤M≤109,h[i]<109,∑h[i]>M
(2)题目分析
- 我们可以直接从小到的枚举 H H H,并计算每一个枚举值对应获得的木材数量即可找到答案,时间复杂度为: O ( n × m a x ( h [ i ] ) ) O(n\times max(h[i])) O(n×max(h[i]))
- 因为时间复杂度的问题,枚举无法满足我们的需要,而此时我们可以发现一个函数关系,随着我们选取的 H H H 增大,我们获得的木材总量越小,这显然是一个还有单调关系的函数。
- 所以我们可以考虑使用二分答案的方式去枚举 H H H,这样时间复杂度为: O ( n log ( m a x ( h [ i ] ) ) ) O(n\log(max(h[i]))) O(nlog(max(h[i])))
(3)核心程序
bool check(ll k)
{
ll sum=0;
for(ll i=1;i<=n;i++)
if(a[i]>k)
sum+=(a[i]-k);
return sum>=m;
}
ll find()
{
ll l=1,r=1e9,ans=-1;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))
{
l=mid+1;
ans=mid;
}
else
r=mid-1;
}
return ans;
}
3.三分——P3382
(1)题目大意
如题所示
(2)题目分析
-
三分法与二分法的基本思想类似,但每次操作需在当前区间 [ l , r ] [l,r] [l,r] 内任取两点 l m i d , r m i d ( l m i d < r m i d ) lmid,rmid(lmid<rmid) lmid,rmid(lmid<rmid)。
-
如果 f ( l m i d ) < f ( r m i d ) f(lmid)<f(rmid) f(lmid)<f(rmid),则在 ⌊ l , l m i d ⌋ \lfloor l,lmid\rfloor ⌊l,lmid⌋ 中函数必然单调递增,极大值所在点必然不在这一区间内,可舍去这一区间。反之亦然。
-
三分法每次操作会舍去两侧区间中的其中一个。为减少三分法的操作次数,应使两侧区间尽可能大。
(3)核心程序
while(fabs(l-r)>=eps)
{
double lmid=(l+r)/2;
double rmid=(mid+r)/2;
if(F(rmid)>F(lmid))
l=lmid;
else
r=rmid;
}