二分查找算法模板
二分模板有两种,适用于不同情况。
整数二分
当区间[l, 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 1;
}
当区间[l, 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 1;
}
实数二分
int bsearch_real(double l, double r)
{
double eps = 1e-6;
//也可以改成循环次数for (int k = 0; k < 100; k++)
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check()) r = mid;
else l = mid;
}
return 1;
}
三分法
我们将要求解的问题设成一个求解曲线极值的问题。我们每次将 l l l和 r r r进行三等分,得到mid_l和mid_r,比较mid_l和mid_r得到三种情况:
- f(mid_l)>f(mid_r) r=mid_r
- f(mid_l)<f(mid_r) l=mid_l
- f(mid_l)=f(mid_r) 且mid_l=mid_r,则得解 , 否则归为以上两类均可
int tsearch(double l, double r){
double mid_l, mid_r, eps=1e-6;
while(r - l < eps){
mid_l = l + (l - r) / 3;
mid_r = l + 2 * (l - r) / 3
if (fun(mid_l) > fun(mid_r)) r=mid_r;
else l=mid_l;
}
return 1;
}
需要注意的是,我们这个曲线一定要是单调的,否则三分法就无效。
二分查找例题
农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于1头,也不会超过2000头。
约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。
围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。
在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
考虑二分查找平均值最大的牛的数量。对于判定条件,我们可以利用之前学过的前缀和来辅助判定。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010;
int n, m;
int cows[N];
double sum[N];
bool check(double avg){
//前缀和
for (int i = 1; i <= n; i++) sum[i] = sum[i-1] + cows[i] - avg;
//判断第j和前缀和是否大于第j-m的前缀和,大于则成立,否则不成立
double minv = 0;
for (int i = 0, j = m; j <= n; i++, j++){
//minv用来存储距离第j-m个数以前的最小值
minv = min(minv, sum[i]);
if (sum[j] >= minv) return true;
}
return false;
}
int main(){
//n代表总的田地数,m代表至少包含的田地数
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> cows[i];
//二分法
double l = 0, r = 2000;
while (r - l > 1e-5){
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
printf("%d\n", int(r * 1000));
}
有N个元素,编号1.2…N,每一对元素之间的大小关系是确定的,关系具有反对称性,但不具有传递性。
注意:不存在两个元素大小相等的情况。
也就是说,元素的大小关系是N个点与N*(N-1)/2条有向边构成的任意有向图。
然而,这是一道交互式试题,这些关系不能一次性得知,你必须通过不超过10000次提问来获取信息,每次提问只能了解某两个元素之间的关系。
现在请你把这N个元素排成一行,使得每个元素都小于右边与它相邻的元素。
你可以通过我们预设的bool函数compare来获得两个元素之间的大小关系。
例如,编号为a和b的两个元素,如果元素a小于元素b,则compare(a,b)返回true,否则返回false。
将N个元素排好序后,把他们的编号以数组的形式输出,如果答案不唯一,则输出任意一个均可。
我们可以尝试,将这种大小关系转换成曲线来表示,假如我们现在将第i个数插入已经排好的数列中,当它比数列中a元素小的时候,那么a就表示成一个下划线;当它比a元素大的时候,就表示成一个上划线。可以表示成下图:
基于此,我们可以利用二分来寻找曲线的极大值,然后插入即可。
// 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) {
//res用来存放排好的数组
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 >> 1;
if (compare(res[mid], i)) l = mid;
else r = mid - 1;
}
res.push_back(i);
//将i插到找到的位置,利用插入排序的方法
for (int j = res.size() - 2; j > r; j--) swap(res[j], res[j + 1]);
//有可能整个曲线是单调递减的,这样的话我们需要补充边界条件
if (compare(i, res[r])) swap(res[r], res[r + 1]);
}
return res;
}
};
通过二分查找可以满足访问次数低于10000次的要求。