# 整数二分
模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)//mid找左边界
{
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)//mid找的是右边界
{
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/
来源:AcWing
总结一下,每一次的判断都会产生l=mid或者r=mid,对于l+r是否要补充1,看l=mid否
如果是l=mid 补1,也就是mid=l+r+1>>1;
关于为什么补1,看原视频讲解
求数的范围(一个数的左右边界)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100005;
int main()
{
int arr[N];
int n, q;
cin >> n >> q;
for (int i = 0; i < n; i++)
cin >> arr[i];
int beg, end;
for (int i = 0; i < q; i++)
{
int x;
cin >> x;
int l = 0, r = n - 1,mid;
while (l < r)//右边界
{
mid = l + r + 1 >> 1;
if (arr[mid] <= x) l = mid;
else r = mid - 1;
}
if (arr[l] != x) cout << "-1 -1" << endl;
else
{
end = l;
l = 0, r = n - 1;
while (l < r)//左边界
{
mid = l + r >> 1;
if (arr[mid] >= x) r = mid;
else l = mid + 1;
}
beg = r;
cout << beg << " " << end << endl;
}
}
return 0;
}
对于为什么是找右边界这一段二分进行讲解:
int mid = l + r + 1 >> 1;
while (l < r)//找右边界
{
if (arr[mid] <= x) l = mid;
else r = mid - 1;
mid = r + l + 1 >> 1;
}
例如数据1 2 3 3 4 4 5 找3
如果arr[mid]<=x 那么要找的数就是再mid的右边 所以l=mid
当arr[mid]>x 要找的数就在mid的左边,并且mid所指的这个数不成立,因为找的是右边界,所以r=mid-1(在mid的左边,可能就是mid往前一位)
而如果找的是左边界
int mid = l + r >> 1;
while (l < r)//找左边界
{
if (arr[mid] >= x) r = mid;
else l = mid + 1;
mid = l + r>> 1;
}
arr[mid]>=x 那么要找的值在mid的左边 且是左边界,所以r=mid
arr[mid]<x 也就是要找的数在mid的右边,所以l=mid+1
(好像理了一遍就清楚了,第一次写的时候不知道为什么那么乱,果然还是菜的原因…)
在比赛的时候有两个非常好用的的函数代替二分算法
(但二分还是要好好学的)
lower_bound(beg,end,num);
从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound(beg,end,num);
从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
浮点数二分
模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
//浮点数的二分能精确取到l和r的中间值,所以不用补1
//整数二分是因为二分是像下取整,如果l=r-1,那么r+l>>1==l,还是区间(l,r)死循环了
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
求数的平方根
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
double x;
cin >> x;
double beg = 0, end = x, mid;
while (end - beg > 1e-8)//只要误差过大就一直缩小误差
{
mid = (beg + end) / 2;
if (mid * mid >= x) end = mid;
else beg = mid;
}
printf("%f",beg);
return 0;
}
这个对于x<1解决不了
- 解决方法1:根据题目给出的数的范围直接把l,r开到最大,例如题目说x范围在0–10000,那么 double beg = 0, end = 10000;
- 解决方法2:处理不了x<1的情况因为x的开方比x大,所以end=max(1,x)这样就可以解决
对于精确值那边的处理,如果题目要求保留四位小数就是1e-6,六位小数就是1e-8,反正精度往上加两位。
求立方根其实差不多,就是把if判断改成midmidmid,注意负数可以开立方
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
double x;
cin >> x;
double beg, end, mid;
beg=-10000,end=10000;
while (end - beg > 1e-8)
{
mid = (beg + end) / 2;
if (mid * mid * mid >= x) end = mid;
else beg = mid;
}
printf("%f",beg);
return 0;
}