整数二分
- 背景: 给定一个单调不减的序列, 每次指定一个数 x ,问 x 在序列中第一次与最后一次出现的位置
- 原理: 根据序列的单调性, 可以把序列分为两个区间, 每次判断中点是否满足要求即可舍去另一半区间
- 时间复杂度: O ( l o g n ) O(log\ n) O(log n)
- 易错点: 区间边界错误导致程序死循环
- Tips: 理解并记忆模板,熟能生巧
下面给出一份二分模板并证明为什么不会出现死循环
整数二分模板
// 数据存在数组a中, 下标从1到n, x为待查询的数
// 第一次出现的位置
int l = 1, r = n;
while(l < r) // 循环终止与 l == r
{
int mid = (l + r) / 2;
if(a[mid] >= x) r = mid;
else l = mid + 1;
}
cout << l;
// 最后一次出现的位置
int l = 1, r = n;
while(l < r) // 循环终止与 l == r
{
int mid = (l + r + 1) / 2;
if(a[mid] <= x) l = mid;
else l = mid - 1;
}
cout << l;
证明
以第一份代码为例, 下面给出一个样例
l
=
1
,
r
=
2
l = 1, r = 2
l=1,r=2
易知
m
i
d
=
(
l
+
r
)
/
2
=
(
1
+
2
)
/
2
=
1
mid = (l + r) / 2 = (1 + 2) / 2 = 1
mid=(l+r)/2=(1+2)/2=1
若
l
=
m
i
d
+
1
l = mid + 1
l=mid+1, 此时
l
=
2
,
r
=
2
l = 2, r = 2
l=2,r=2, 正常退出循环
修改模板为
l
=
m
i
d
l = mid
l=mid, 此时
l
=
1
,
r
=
2
l = 1, r = 2
l=1,r=2, 代码一直循环求mid无法退出
根据样例可知, 每次求 m i d mid mid 修改区间后, 新区间必然要比原区间更小, 否则就会出现死循环
欲证明原命题, 即证当
l
<
r
l < r
l<r 时:
m
i
d
<
r
mid < r
mid<r
m
i
d
+
1
>
l
mid + 1 > l
mid+1>l
根据代码可知
m
i
d
=
⌊
l
+
r
2
⌋
≤
⌊
r
−
1
+
r
2
⌋
=
r
−
1
<
r
mid = \lfloor \frac{l + r}{2} \rfloor \leq \lfloor \frac{r - 1 + r}{2} \rfloor = r - 1 < r
mid=⌊2l+r⌋≤⌊2r−1+r⌋=r−1<r
m
i
d
+
1
=
⌊
l
+
r
+
2
2
⌋
>
=
⌊
l
+
l
+
1
+
2
2
⌋
=
l
+
1
>
l
mid + 1 = \lfloor \frac{l + r + 2}{2} \rfloor >= \lfloor \frac{l + l + 1 + 2}{2} \rfloor = l + 1 > l
mid+1=⌊2l+r+2⌋>=⌊2l+l+1+2⌋=l+1>l
证毕
例题
Acwing 791: 数的范围
AC code
#include <iostream>
using namespace std;
const int N = 1E5 + 10;
int n, m;
int a[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> a[i];
while(m -- )
{
int l = 1, r = n, x;
cin >> x;
while(l < r)
{
int mid = (l + r) / 2;
if(a[mid] >= x) r = mid;
else l = mid + 1;
}
if(a[l] != x) cout << "-1 -1\n";
else
{
cout << l - 1 << ' ';
int l = 1, r = n;
while(l < r)
{
int mid = (l + r + 1) / 2;
if(a[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l - 1 <<'\n';
}
}
return 0;
}
实数二分
- 背景: 求数的立方根
- 原理: 根据函数的单调性, 可以将当前的区间分为两个区间, 每次判断中点来舍去一半区间
- 时间复杂度: O ( l o g n ) O(log\ n) O(log n)
- 易错点: eps过大导致精度不够或者eps过小导致运行时间长
- Tips: eps一般取题目要求精度的 1 % 1\% 1%
模板
double l = 0, r = 1E9, eps = 1E-5;
while(r - l > eps)
{
double mid = (l + r) / 2;
if(mid * mid * mid >= x) r = mid;
else l = mid;
}
实数二分与整数二分不同, 不会因为区间边界问题导致死循环
样例
Acwing 790: 数的三次方根
AC code
#include<cstdio>
using namespace std;
double n;
int main()
{
scanf("%lf", &n);
double l = -1E4, r = 1E4, eps = 1E-8;
while(r - l >= eps)
{
double mid = (l + r) / 2;
if(mid * mid * mid >= n) r = mid;
else l = mid;
}
printf("%.6lf", l);
return 0;
}