前言
二分在算法竞赛方面是一个非常重要的算法, 虽然算法思想与实现都是比较简单易懂的, 但是真正的代码实现,往往都是十分代码九份有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+(r−l)/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;
}
}