模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
// 求左端点,红色箭头
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, 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;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
二分的本质
引用yxc的话,单调性并不是二分的本质,二分的本质是:用某个条件将区间划分为两部分,然后两种不同的二分方法分别是找到划分区间的那两个端点。
首先定义一个条件,也就是
c
h
e
c
k
(
)
check()
check()函数,这个条件定义了一个区间左半段满足条件,右半段不满足条件。
绿色箭头也就表示了左半段的最右端点,红色箭头也就表示了右半段的最左端点。
满足条件的最右端点,绿色
此时
m
i
d
=
(
l
+
r
+
1
)
/
2
mid=(l+r+1)/2
mid=(l+r+1)/2,
+
1
+1
+1是为了让
r
−
l
=
1
r-l=1
r−l=1时
l
l
l能够向右移动,也就是增大
l
l
l。
i
f
(
c
h
e
c
k
(
m
i
d
)
)
if(check(mid))
if(check(mid)),则
m
i
d
mid
mid在绿色区间中,应该移动左端点,且
m
i
d
mid
mid是满足条件的,
l
=
m
i
d
l=mid
l=mid,继续在
[
m
i
d
,
r
]
[mid,r]
[mid,r]中搜寻;
e
l
s
e
else
else,则
m
i
d
mid
mid在红色区间中,应该移动右端点,
m
i
d
mid
mid不满足条件,
r
=
m
i
d
−
1
r=mid-1
r=mid−1,继续在
[
l
,
m
i
d
−
1
]
[l,mid-1]
[l,mid−1]中搜寻。
不满足条件的最左端点,红色
此时
m
i
d
=
(
l
+
r
)
/
2
mid=(l+r)/2
mid=(l+r)/2,不
+
1
+1
+1是为了让
r
−
l
=
1
r-l=1
r−l=1时
r
r
r能够向左移动,也就是减小
r
r
r。
i
f
(
c
h
e
c
k
(
m
i
d
)
)
if(check(mid))
if(check(mid)),则
m
i
d
mid
mid在绿色区间中,应该移动左端点,且
m
i
d
mid
mid满足条件,
l
=
m
i
d
+
1
l=mid+1
l=mid+1,继续在
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r]中搜寻;
e
l
s
e
else
else,则
m
i
d
mid
mid在红色区间中,应该移动右端点,
m
i
d
mid
mid不满足条件,
r
=
m
i
d
r=mid
r=mid,继续在
[
l
,
m
i
d
]
[l,mid]
[l,mid]中搜寻。
例题
789. 数的范围
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int n, q;
// 找的是第一个出现的位置
int bsearch_l(int x) {
int l = 0, r = n-1, mid;
while (l < r) {
// 求大于等于x的第一个地方,左半段a[i]<x,右半段a[i]>=x,求右端点
mid = (l + r) / 2;
if (a[mid] < x) {
l = mid + 1;
} else {
r = mid;
}
}
if (a[l] == x) return l;
else return -1;
}
// 找的是最后一个位置
int bsearch_r(int x) {
int l = 0, r = n-1, mid;
while (l < r) {
// 求左端点,左半段a[i]<=x
mid = (l + r + 1) / 2;
if (a[mid] <= x) {
l = mid;
} else {
r = mid-1;
}
}
if (a[l] == x) return l;
return -1;
}
int main() {
cin >> n >> q;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
while (q--) {
int x;
cin >> x;
cout << bsearch_l(x) << " " << bsearch_r(x) << endl;
}
}