二分算法模板(整数型和浮点型)
目录
模板
二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
如何选择整数型的模板:由check函数判断当check()为真时,是r=mid还是l=mid,由此判断模板类型
版本1(左边界)
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2(右边界)
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。
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;
}
模板3(浮点型)
1.不需要考虑边界
2.while的判断条件是(r - l > INF),INF是答案要求保留的小数在乘1e-2
#include <iostream>
#include <algorithm>
using namespace std;
const double INF = 1e-8; //INF精度比要求的精度*1e-2
int main()
{
double x;
cin >> x;
double l = -10000, r = 10000; //因为x有可能是负数,所以l和r不能赋值为-x,x
while(r - l > INF)
{
double mid = (l + r) / 2; //浮点数不支持 >> 运算
//因为是小数,所以不需要+1或者-1,没有边界问题
if(mid * mid * mid > x) r = mid;
else l = mid;
}
printf("%.6f\n", l);
return 0;
}
例题:
1.double二分
思路
1. 答案要求查找一个大于F的区间(即围起来的田地)内牛的平均数的最大值,通过数据容易得出这个值的范围是【1-2000】,满足有序性,所以遍历答案区间【1-2000】,二分查找答案
用check()函数判断答案是否有效,如果有效,L = mid,否则 R = mid
关于二分while的结束条件:因为答案的最大值是2e3,所以说答案需要精确到小数点后三位,但是为了确保答案的一定正确,这里可以精确的1e-5,一般最大精确到1e-7就够了,
2.check(double ans)函数:判断是否有一个范围大于F的区间,且该区间牛的平均值大于ans,显而易见这里需要用到前缀和,但如果直接对牛的数量进行前缀和的话,最后还需要除去这个区间的范围,相当麻烦
所以,这里有一个优化的一个小技巧:对于每一个牛a【i】,让他减去这个平均值ans再进行前缀和,然后就可以直接判断,不需要和区间范围再进行运算
那么如何找到这个区间呢?暴力做法是O(n2)遍历保存没一块地牛的数量的数组a,然后找到一个右边界 j 和一个左边界 l ,使得 r - l > F,且平均值大于ans,这显然是不行的,这里就涉及了另一个优化技巧:在一个for循环内实现遍历操作,新增一个保存最小值的变量minv,用来保存左边界的最小值,因为在不断向右遍历的过程中,左边界的值可以变大也可能变小,所以只需要保存最小的那个边界值的边界 l ,然后从 F 开始遍历右边界 j ,如果在遍历的过程中 a[j] - minv >= 0,即找到这么一个区间了,那么返回 true,如果一直遍历到右边界还没有找到,返回false
3.答案的输出:因为答案要求向下取整,所以对二分的输出就有要求,那么为什么最后为什么输出int(r1000),而不是int(l1000)呢?这里说一下我的理解:
当我们用实数二分求一个值的时候,这个值会趋近于答案,比如答案是6500,而l等于6499.99999,r等于6500.00001,随着精度的提高,这两边会越来越趋近于6500,这就是实数二分的特点。
因为它趋近于6500,所以我们知道其实答案就是6500,而答案6500向下取整就是6500,所以我们必须用 r 向下取整来得到答案(因为r比答案要大一点点)。
同理,如果题目要求最终答案向上取整,就必须用 l 向上取整来得到最终答案(因为 l 比答案小一点点)。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100010;
int a[N];
double sum[N];
int n,f;
bool check(double mid)
{
for(int i= 1; i <= n; i ++)
sum[i] = sum[i - 1] + a[i] - mid;
double minv = 0;
for(int i = 0,j = f; j <= n; j ++,i++)
{
minv = min(sum[i], minv);
if(sum[j] >= minv)
return true;
}
return false;
}
int main()
{
cin >> n >> f;
for(int i = 1; i <= n; i ++)
cin >> a[i];
double l = 1, r = 2000;
while(r - l > 1e-5)
{
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%d",int(r * 1000));
return 0;
}
2.二分插入排序
题目连接:113. 特殊排序 - AcWing题库
// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.
class Solution {
public:
vector<int> specialSort(int N) {
vector<int> res;
res.push_back(1);
for(int i = 2; i <= N; i ++)
{
int l = 0,r = res.size() - 1;
while(l <= r)
{
int mid = l + r >> 1;
if(compare(i,res[mid])) r = mid - 1;
else l = mid + 1;
}
res.push_back(i);
for(int j = res.size() - 2; j > r; j --)
swap(res[j], res[j + 1]);
}
return res;
}
};
3.二分查找左右边界
题目链接:789. 数的范围 - AcWing题库
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N], n, q;
int main()
{
cin >> n >> q;
for(int i = 0; i < n; i ++ ) cin >> a[i];
while(q -- )
{
int k;
cin >> k;
//查找左边界
int l = 0, r = n - 1;
while(l < r)
{
int mid = l + r >> 1;
if(a[mid] > k) r = mid - 1;
else if(a[mid] == k) r = mid;
//上面两步可以合并为if(a[mid] >= k) r = mid;
else l = mid + 1;
}
if(a[l] == k) cout << l << " ";
else cout << -1 << " ";
//查找右边界
l = 0, r = n - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(a[mid] < k) l = mid + 1;
else if(a[mid] == k) l = mid;
//上面两步可以合并为if(a[mid] <= k) l = mid;
else r = mid - 1;
}
if(a[l] == k) cout << l << " ";
else cout << -1 << " ";
cout << endl;
}
return 0;
}