[模板]整数二分
整数二分有两个模板。
int bsearch_1(int l, int r) {
while(l < r) {
int mid = l + 1 >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
这种写法每次把区间压缩到[l, mid]或[mid + 1, r]。
int bsearch_2(int l, int r) {
while(l < r) {
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
这种写法每次把区间压缩到[l, mid - 1]或[mid, r]。
如何确定用哪个模板?
取决于check的逻辑,如果我们发现我们需要更新区间为[l, mid]或[mid + 1, r],就用模板1.
如果我们发现我们需要更新区间为[l, mid - 1]或[mid, r],就用模板2.
来看一道题,acwing789.数的范围
寻找数x的起始位置时,需要把q[mid]和x进行比较,如果q[mid] >= x,说明x的位置,要么在mid的左边,要么就是mid,
因为我们要找的是x的最靠左的位置(起始位置),所以我们要往左边搜索,因为q[mid]有可能和x相等,所以也不能把mid跳过,因此更新区间为r = mid;
如果q[mid] < x,说明x的位置在mid的右边(不包括mid),所以往右边搜索,l = mid + 1;
这两个更新的区间,就决定了我们用的是模板1。
实际上,有了r = mid; 就不用判断l了,肯定是l = mid + 1;
寻找x的终止位置时,我们也是把q[mid]和x进行比较,如果q[mid] <= x,说明我们要找的位置肯定在mid的右边(或者就是mid),
我们需要往右边搜索,由于有可能是mid,所以我们不能把mid跳过,因此有l = mid;
如果q[mid] > x,则往左边搜素,r = mid - 1;
这两个更新区间,决定了我们用模板二。
模板一和模板二的主要区别在于,mid = l + r >> 1还是mid = l + r + 1 >> 1,这个主要和整除有关,整除是下取整,所以如果更新的区间是l = mid, 就要加一,是r = mid,就不用加一,就这么记就行了~
这道题的代码如下;
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int q[N];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 0; i < n; ++i) {
scanf("%d", &q[i]);
}
while(m--) {
int x;
scanf("%d", &x);
int l = 0, r = n - 1;
while(l < r) {
int mid = l + r >> 1; //找x的起始位置,由于更新区间时r=mid,所以用模板1, 所以这里mid定义为l + r >> 1
if(q[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
if(q[l] != x) { //如果不存在x,直接返回-1
printf("-1 -1\n");
} else { //否则,我们还需要找x的终止位置
printf("%d ", l);
int l = 0, r = n - 1;
while(l < r) {
int mid = l + r + 1 >> 1; //找x的终止位置,由于更新区间时l = mid,所以这里mid定义为l + r + 1 >> 1
if(q[mid] <= x) {
l = mid;
} else {
r = mid - 1;
}
}
printf("%d\n", l);
}
}
return 0;
}