一、二分算法思想
二分的本质是边界划分而不是单调性,有单调性一定可以使用二分,没有单调性也可以使用二分。二分是寻找边界,将整个区间一分为二,如左半边区间满足,右半边区间不满足。二分找的是一个分界点。
二分一定有解,但是不一定满足题意。二分寻找的是边界,一定会存在分界点。
二、整数二分模板
注意事项:
区间划分不重叠,左半部分区间为[l,mid]右半部分区间为[ mid+1,r]。
mid值更新:在找左半边区间分界点的时候,如果check()结果为true,那么答案一定在mid的右边并且包括mid,所以为true时 r=mid;为false时,答案一定在mid左边,并且不包括mid,所以为false时l=mid+1;同理可推寻找右边区间分界点时的结果。
求左端和右端分界点,mid取值不同。一个小口诀,男左女右(判断为true时) 男是一 所以加一 女是零所以不用加 (真的很好用)。
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;
}
模板题:https://www.acwing.com/problem/content/791/
注意输出顺序
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
while(m--)
{
int q;
scanf("%d",&q);
int l=0,r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(a[mid]>=q) r=mid;
else l=mid+1;
}
if(a[l]!=q) printf("-1 -1\n");
else
{
printf("%d",l);
l=0,r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<=q) l=mid;
else r=mid-1;
}
printf(" %d\n",l);
}
}
return 0;
}
三、浮点数二分模板
注意事项:
划分区间可以重复,区间更新时取mid即可。
精度问题,一般多保留两位。
注意定义double类型。
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;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
模板题:https://www.acwing.com/problem/content/792/
#include<bits/stdc++.h>
using namespace std;
double n;
int main()
{
cin>>n;
double l=-10000,r=10000;
while(r-l>1e-8)
{
double mid=(l+r)/2;
if(mid*mid*mid>=n) r=mid;
else l=mid;
}
printf("%.6lf",l);
return 0;
}
四、小知识
1.题目要求保留六位小数,如果使用1e10-7,那么因为四舍五入第八位到第七位已经产生了误差,从而导致输出第六位时会有误差积累,而使用1e10-8,则四舍五入第九位,对第八位造成误差,但第七位和第六位是精确的,所以通常多保留两位
2.printf默认输出double小数点后六位