二分写法选讲

二分写法总结

众所周知,二分是一种特别常用的求解答案的方式。无论是二分查找,还是二分答案,都通过二分思想把求解转化成了判定。这样就大大减少了思维难度,并大大减少了复杂度。但实现二分并不是我们想象的那样简单,实现二分的难点是判定函数的书写以及二分循环的写法,判定函数需要根据题目的类型不同具体类型具体分析。那么我们这篇随笔就着重讲解一下二分的写法,以便让大家拍对二分,拍熟二分。

本篇随笔是功能型随笔。默认读者已经明白二分思想并能手写二分,只是对二分到底该怎么写,怎么避免死循环等问题尚有困惑,如果对二分还不了解的读者,请先行补习二分。

  • 二分书写的要点

二分有很多种写法,如果翻看一篇二分题目的题解,我们会发现不同的AC代码中二分写的五花八门。但是他们的结果都是对的。那么,二分书写的要点到底是什么呢?

首先,是循环条件。这个条件是二分书写的重中之重,因为这个条件如果写不对的话,就可能导致二分进行过程中无法退出循环。即导致死循环的情况,而这个条件也决定了二分进行过程中缩小解集范围的书写方式。

然后,是缩小方式。所谓缩小方式,就是缩小下一步要二分的区间,也就是把\(l,r\)的值变成\(mid\),但是这个变化暗藏杀机。如果和循环条件配合不好的话,非常容易直接死循环,或者造成\(mid\)上的答案取不到(假如答案就是这个\(mid\)的话就惨了)等后果。

最后,是退出条件。退出二分循环的条件一定要确定好,这样才能确定答案的位置。并且也可以通过算法分析来避免死循环。

  • 我的二分写法

注:我并不是在推销自己的二分写法,我觉得如果是一个已经有了一个成型二分写法的选手来看本博客的话,学习到的东西约等于0。所以,为了面向更广大的对二分书写极为蒙圈的选手比如说我,在此介绍和推导我的二分写法,如果各位看官觉得有理,就直接采纳我的写法吧。顺手求推荐

我的二分循环模板大体是这样:

模板1:

while(l<r)
{
	int mid=(l+r)>>1;
	if(check(mid))
		l=mid+1;
	else
		r=mid;
}

模板2:

while(l<r)
{
	int mid=(l+r+1)>>1;
	if(check(mid))
		l=mid;
	else
		r=mid-1;
}

是的,这是两套写法。注意是两套写法,而不是两种写法,这两种写法会有很大的区别。首先是区间的缩小上,左端点和右端点的移动就不一样,其次,mid的取法也不一样。那么,这两种写法到底有什么不同呢?

首先我们关注这个区间缩小上的不同,我们对一个mid值进行判断的时候,如果它合法,那么就要根据题意左移或者右移区间,但是这个mid到底有没有可能是答案呢?这要依题目而定,如果这个mid可能是答案,那么在移动区间的时候就要把这个mid值也包含进来,否则就要\(\pm 1\)

通过以上的叙述,我们再来关注第二点不同,mid的取值方式,假如我们的mid可能是答案,那么我们再次移动区间的时候,就可以直接除以2,因为这时的左移/右移区间时把mid已经包含进去了,所以无论最后区间缩小到什么程度(我们分析时一般认为最小区间只有两个整数),都不会造成死循环,因为在最小情况下\(mid=\lfloor l+r\rfloor\)会得出一个确切值,我们就能通过对mid的check来确定答案是否是它。但是如果mid不可能是答案,缩小区间的时候需要\(\pm1\)的情况下,这么直接/2,取整之后会导致区间不会缩小,这样就会造成死循环,对应的解决办法是这样:\(mid=\lfloor l+r+1\rfloor\)。这样就保证了死循环情况绝对不会出现。

  • 实数二分

实数二分就不需要考虑上面那么多的问题,我们只需要考虑循环条件。其实也很简单,因为我们二分的是实数,所以需要考虑精度损失的问题。那么我们的循环条件就应该是这个不能损失的精度,一般我们把eps(精度)定义成1e-5,个别题目需要更加精确,请大家具体分析。

实数二分还需要注意,这里的mid取的时候绝不能使用算数右移>>,因为这个右移是针对整数的(因为是位运算嘛!)

大概模板是这样:

while(r-l>eps)
    {
        long double mid=(l+r)/2;
        if(check(mid))
            r=mid;
        else
            l=mid;
    }
  • 总结

二分方法千万种,保证正确是首要。

函数正确二分炸,只剩退役路一条。

posted @ 2019-09-04 19:07  Seaway-Fu  阅读(802)  评论(4编辑  收藏  举报