浅谈如何写出一个正确的二分查找算法

前言

二分在算法竞赛方面是一个非常重要的算法, 虽然算法思想与实现都是比较简单易懂的, 但是真正的代码实现,往往都是十分代码九份有BUG.

博主在考研复习中又重新学习了这个两年前貌似都掌握的算法, 在这里写下这个文章作为学习笔记, 也希望可以帮助到更多的人写出可以在更多的场景下无BUG的二分.

C++的内置函数-求下界

lowerbound() 与 upperbound()
这两个函数的返回值分别为
返回大于等于查找的key值的地址 - lowerbound
严格大于key的值的地址- upperbound

其中 lowerbound 的简单实现

while (l < r) {
        int mid = l+(r-l)/2;
        if(a[mid] < key)
            l = mid+1;
        else
            r = mid;
    }

upperbound

while (l < r) {
        int mid = l+(r-l)/2;
        if(a[mid] <= key)
            l = mid+1;
        else
            r = mid;
    }

这两个函数实现基本一样就是 为了寻找满足条件(大于or大于等于key)的最小值,即寻找的值从左向右越靠右越满足.
w h i l e while while循环内部主要维护的是类似指针一般的 l l l, 考虑循环结束条件 l = = r l==r l==r, 这个时候我们主要要研究 l + 1 = = r l+1==r l+1==r的时候的情况(循环结束的上一步), 此时 l = m i d = l + ( r − l ) / 2 l=mid=l+(r-l)/2 l=mid=l+(rl)/2, 那么就正好符合条件,寻找到查找的值.

上述的两个函数的思想是一样的, 即在从左向右越向右越满足条件,我们寻找的时候寻找的是一个下界.

但是有的时候我们寻找的可能是一个上界, 即越考左的值越满足条件, 求的是最右的值, 这个寻找的原理是一样的,只需要将到达边界问题时候的代码稍微修改一下就好啦

求上界

下方代码求的是最大的值小于key的值的大小

 while (l < r) {
        int mid = l+(r-l+1)/2;
        if(a[mid] < key)
            l = mid;
        else
            r = mid-1;
    }

这个时候我们发现 我们维护的值已经变成啦 r r r, 这是因为这个和求下届的方向是相反的, 需要 r r r不断的向左逼近, 所以在结束条件上一步 l + 1 = = r l+1==r l+1==r的时候, m i d mid mid的值必须要和 r r r相等才行

浮点数二分

这种情况很简单 所求的是个精度而已 多循环几次就好, 边界什么的压根不需要考虑

for(int i = 1; i <= 100; ++i)
        int mid = (l+r)/2;
        if(a[mid] <= key)
            l = mid;
        else
            r = mid;
    }

例题

求下界

hiho#1095 : HIHO Drinking Game
题目链接:http://hihocoder.com/problemset/problem/1095?sid=1471983
题意: 小hi和小ho玩游戏,小hi每轮游戏向桶内倒入T单位的水, 然后小ho回掷骰子得到一个d值, 如果桶内的水大于d那么小ho得一分,并且将桶内的水喝下去d, 否则小hi会得到1分,并且桶内的水会直接清零.

目前小ho开挂知道了他每轮游戏得到的d值的大小, 求出最小的T

分析: 当T=0的时候 小ho每轮都会输, 但是T=k+1的时候, 小ho每轮都会赢,
大胆猜测小ho获得的分数符合单调递增的特性, 那么我们所求的值就是找一个下界, 这个下界>n即可, 即为upperbound()
AC代码

import java.io.*;
import java.util.*;
import java.math.*;


public class Main {
	public static int[] d = new int[100010];
	public static int n;
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		
		while (cin.hasNext()) {
			n = cin.nextInt();
			int k = cin.nextInt();
			for (int i = 1; i <= n; ++i) {
				d[i] = cin.nextInt();
			}
			int l = 0, r = k+2;
			while (l < r) {
				int mid = l+(r-l)/2;
				if(check(mid) <= n/2) 
					l = mid+1;
				else
					r = mid;
			}
			System.out.println(l);
		}
		
	}
	
	public static int check(int x) {
		int s = 0, res = 0;
		for(int i = 1; i <= n; ++i) {
			res += x;
			if(res > d[i]) {
				res -= d[i];
				s++;
			}
			else 
				res = 0;
		}
		return s;
	}
	
}

求上界

ZOJ4062 Plants vs. Zombies
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4062

题意: 给出一个1-n的植物, 一个机器人从0点开始对这个植物浇水, 每次浇水必须不停的移动, 每个植物浇水一次会生长 a [ i ] a[i] a[i]的值, 在最多不能超过m次的步数中,问这片植物最低的值最多是多少

分析:这个是18青岛Region的一道铜牌题, 最大化最小值问题, 二分求上界,
难点在于怎么浇水的问题,仔细思考后我们会发现, 贪心的浇水,每个植物要有一个下界, 左右横跳肯定是最优解啦.

AC代码
代码内有两份二分 其中注释的是求下界然后减去1, 思考一下, 求最大上界不就是求最小的下界再向左移一位么

#include <bits/stdc++.h>

using namespace std;

#define ll long long
const int MAXN = 2e5+10;
ll n,m;
ll a[MAXN], b[MAXN];
bool check(ll x) {
    if(x==0) return true;
    ll xx=0;
    for(int i=0;i<n+10;++i) b[i]=0;
    for(int i=1; i<=n; ++i) {
        if(b[i] >= x) {
            if(i != n)
                xx++;
            continue;
        }
        ll zz=(x-b[i]+a[i]-1)/a[i];
        b[i+1] += (zz-1)*a[i+1];
        xx += zz;
        xx += zz-1;
        if(xx > m) return false;
    }
    return xx<=m;
}

int main() {
   // freopen("in.txt", "r", stdin);
    ios::sync_with_stdio(false);
    int T; cin>>T;
    while(T--) {
        cin>>n>>m;
        for(ll i=1;i<=n;++i) cin>>a[i];
        ll l=0,r=(ll)1e18;
//        ll ans=0;
//        while(l < r) {
//            ll mid = l+(r-l)/2;
//            if(check(mid)) {
//                ans = mid; l = mid+1;
//            }
//            else
//                r = mid;
//        }
//        cout<<ans<<"\n";

        while(l < r) {
            ll mid = l+(r-l+1)/2;
            if(check(mid)) {
                l = mid;
            }
            else
                r = mid-1;
//            printf("%d %d %d\n",l, r, mid);
        }

        cout<<l<<"\n";
    }


return 0;
}

浮点数

题目链接: https://hpuoj.com/contest/7/problem/J/
分析: 其实就是求的一个下界而已 无脑循环100次肯定可以找到,

import java.util.*;
import java.io.*;
import java.math.*;

public class Main {
	public static int a, b, c;
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		int T = cin.nextInt();
		while(T --> 0) {
			double R, x1, y1, H, x2, y2;
			
			R = cin.nextDouble();
			x1 = cin.nextDouble();
			y1 = cin.nextDouble();
			H = cin.nextDouble();
			x2 = cin.nextDouble();
			y2 = cin.nextDouble();
			a = cin.nextInt();
			b = cin.nextInt();
			c = cin.nextInt();
			double len = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
			double l = Math.abs(len-R), r = len+R;
			double eps = 1e-8;
			double ans; ans = -1.0;
			
			for(int i = 0; i < 100; ++i) {
				double mid = (l+r)/2;
				if(check(mid)<H) l = mid;
				else {
					r = mid; ans = r;
				}
			}
			
			if(ans == -1) 
				System.out.println(-1);
			else 
				System.out.println(String.format("%.2f", l));
		}
	}
	public static double check(double l) {
		return a*l*l+b*Math.sqrt(l)+c;
	}
	
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值