一种手打二分模板的推荐
你是否还为手打二分的几个细节苦恼?不知道该用哪种?
- 到底是
l < r
还是l<=r
? - 到底是
mid=(l+r)/2
还是mid=(l+r)>>1
还是mid=(l+r+1)>>1
? - 到底是
l=mid+1
还是l=mid
? - 到底是
r=mid-1
还是r=mid
? - 到底是
ans=l
?还是l+1
?亦或是l-1
? - 初始边界
l=0
还是1
?r=n
还是n+1
? - \(\cdots\) \(\cdots\)
我可以提供一个范本:
//sample 1
while(l < r){
mid = (l + r) >> 1;
if(check(mid)) l = mid + 1;
else r = mid;
}
//sample 2
while(l < r){
mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
考虑到这样进行左右区间划分与线段树相同,习惯上较为有利,我总结出了默认使用这种写法的情况下一些细节设置:
关键点:考察check
函数返回值序列与我们所求答案位置的关系。
\(1.\) 若check
函数返回为真时,收缩至左区间(r=mid
),则check
函数返回值序列形如0,0,...,0,1,1,...,1
,终止条件l==r
将指向第一个1
的位置。
- 如果答案位置为第一个
1
的出现位置,则ans=l
。 - 如果答案位置为最后一个
0
的出现位置,则ans=l-1
。
\(2.\) 若check
函数返回为真时,收缩至右区间(l=mid+1
),则check
函数返回值序列形如1,1,...,1,0,0,...,0
,终止条件l==r
将指向第一个0
的位置。
- 如果答案位置为第一个
0
的出现位置,则ans=l
。 - 如果答案位置为最后一个
1
的出现位置,则ans=l-1
。
二分的初始边界应该设置为 \([1,n)\),即l=1,r=n+1
。