15.二分法

一、算法内容

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+1r=mid+1都可能根据答案的实际需要进行修改,它们从属于哪一个if条件判断也需要具体情况具体分析。

二、实例分析

1.二分查找——最简单的例子

(1)背景

假设我们拥有一个长度为 n n n的升序排列的数组,现在题目要求我们在数组中查找某一个数,并期望我们能在 O ( l o g n ) O(logn) O(logn)的时间内完成查找。下面,我们将结合这个例子具体讲解二分查找的使用方式。

(2)分析

二分查找的具体过程是:它每次将会考察我们选择的参照点(中心点)来进行下一步的判断。而在本题中,参照点就是我们这个升序数组当前部分的中间元素。

  • 如果中间元素刚好是要找的,就结束搜索过程
  • 如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找
  • 如果中间元素大于所查找的值,那么右侧的指挥更大,不会有所查找的元素,只需到左侧查找

因为在二分搜索过程中,算法每次都把查询的区间减半,所以对于一个长度为 n n n的数组,至多会进行 l o g n logn 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 1N106,1M109,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 l o g ( m a x ( h [ i ] ) ) ) O(nlog(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(lmidlmid,rmid(lmid<rmid)

  • 如果 f ( l m i d ) < f ( r m i d ) f(lmid)f(lmid)<f(rmid),则在 ⌊ r m i d , r ⌋ \lfloor rmid,r\rfloor rmid,r中函数必然单调递增,最小值所在点必然不在这一区间内,可舍去这一区间。反之亦然。

  • 三分法每次操作会舍去两侧区间中的其中一个。为减少三分法的操作次数,应使两侧区间尽可能大。

(3)核心程序

while(fabs(l-r)>=eps)
{
	double mid=(l+r)/2;
	double mmid=(mid+r)/2;
	if(F(mmid)>F(mid))
		l=mid;
	else
		r=mmid;
}

三、作业

1.橙题

P1102 A-B 数对

P2249 【深基13.例1】查找

P1024 [NOIP2001 提高组] 一元三次方程求解

P3382 【模板】三分法

2.黄题

P1873 [COCI 2011/2012 #5] EKO / 砍树

P4058 [Code+#1]木材

P1182 数列分段 Section II

P2678 [NOIP2015 提高组] 跳石头

P2759 奇怪的函数

你可能感兴趣的:(算法竞赛讲义,算法,c++,二分)