Table of Contents
IntroductionBinary Searching on Monotonic FunctionsFinding The Maximum x Such That f(x) = trueImplementation 1Implementation 2Finding The Minimum x Such That f(x) = trueExample - Maximum MedianCommon MistakesMistake 1 - Off By OneMistake 2 - Not Accounting for Negative BoundsMistake 3 - Integer OverflowLibrary Functions For Binary SearchExample - Counting HaybalesWith Built-in FunctionWithout Built-in FunctionProblemsUSACOGeneralEdit with LiveUpdateBinary Search
Authors: Darren Yao, Abutalib Namazov, Andrew Wang, Qi Wang
Prerequisites
Binary searching on arbitrary monotonic functions and built-in functions for binary search.
Table of Contents
IntroductionBinary Searching on Monotonic FunctionsFinding The Maximum x Such That f(x) = trueImplementation 1Implementation 2Finding The Minimum x Such That f(x) = trueExample - Maximum MedianCommon MistakesMistake 1 - Off By OneMistake 2 - Not Accounting for Negative BoundsMistake 3 - Integer OverflowLibrary Functions For Binary SearchExample - Counting HaybalesWith Built-in FunctionWithout Built-in FunctionProblemsUSACOGeneralEdit with LiveUpdate
Introduction
Resources | |||
---|---|---|---|
CSA | animation, code, lower_bound + upper_bound | ||
CPH | code, lower_bound + upper_bound, some applications | ||
CF | videos, problems similar to those covered in this module | ||
KA | plenty of diagrams, javascript implementation | ||
IUSACO | module is based off this | ||
TC | similar material | ||
LC | many problems & applications |
When we binary search on an answer, we start with a search space of size which we know the answer lies in. Then each iteration of the binary search cuts the search space in half, so the algorithm tests values. This is efficient and much better than testing each possible value in the search space.
Binary Searching on Monotonic Functions
Let's say we have a boolean function f(x)
. Usually, in such problems, we want to find the maximum or minimum value of such that f(x)
is true. Similarly to how binary search on an array only works on a sorted array, binary search on the answer only works if the answer function is monotonic, meaning that it is always non-decreasing or always non-increasing.
Finding The Maximum x
Such That f(x) = true
We want to construct a function lastTrue
such that lastTrue(lo, hi, f)
returns the last x
in the range [lo,hi]
such that f(x) = true
. If no such x
exists, then lastTrue
should return lo-1
.
This can be done with binary search if f(x)
satisfies both of the following conditions:
- If
f(x) = true
, thenf(y) = true
for all . - If
f(x) = false
, thenf(y) = false
for all .
For example, if f(x)
is given by the following function:
f(1) = true f(2) = true f(3) = true f(4) = true f(5) = true f(6) = false f(7) = false f(8) = false
then lastTrue(1, 8, f) = 5
and lastTrue(7, 8, f) = 6
.
Implementation 1
Verify that this implementation doesn't call f
on any values outside of the range [lo,hi]
.
C++
#include <bits/stdc++.h>using namespace std;int lastTrue(int lo, int hi, function<bool(int)> f) {for (--lo; lo < hi; ) {int mid = lo+(hi-lo+1)/2;// find the middle of the current range (rounding up)if (f(mid)) lo = mid;// if mid works, then all numbers smaller than mid also workelse hi = mid-1;
See Lambda Expressions if you're not familiar with the syntax used in the main function.
Java
import java.util.function.Predicate;public class BinarySearch {public static void main(String[] args) {System.out.println(lastTrue(2,10,(x) -> true)); // 10// all numbers satisfy the conditionSystem.out.println(lastTrue(2,10,(x) -> x*x<=30)); // 5System.out.println(lastTrue(2,10,(x) -> false)); // 1// no numbers satisfy the condition}
See Java Predicate Interface if you're not familiar with the Predicate
interface used.
Python
def lastTrue(lo, hi, f):""" Binary Search:param f: (lambda function) check a state:param lo: (int) lower bound:param hi: (int) upper bound:return res: (int) the maximum x such that f(x) is true"""res = lo-1while lo <= hi:mid = (lo+hi)//2 # find the middle of the current range
See Lambda Expressions if you're not familiar with the syntax used in the program.
Implementation 2
This approach is based on interval jumping. Essentially, we start from the beginning of the array, make jumps, and reduce the jump length as we get closer to the target element. We use powers of 2, very similiar to Binary Jumping.
C++
int lastTrue(int lo, int hi, function<bool(int)> f) {for (int dif = (hi-(--lo)); dif; dif /= 2)while (lo+dif <= hi && f(lo+dif)) lo += dif;return lo;}
Java
public static int lastTrue(int lo, int hi, Predicate<Integer> f) {for (int dif = (hi-(--lo)); dif > 0; dif /= 2)while (lo+dif <= hi && f.test(lo+dif)) lo += dif; //f is the functionreturn lo;}
Python
Warning!
def lastTrue(lo, hi, f): # python scoping...lo-=1locs=locals()while locs["lo"] < locs["hi"]:locs["mid"] = (locs["lo"]+locs["hi"]+1)//2exec("lo = mid" if f(locs["mid"]) else "hi = mid-1",globals(),locs)return locs["lo"]
This code is very cumbersome because we have to use exec
function to run the code lo = mid
and hi = mid - 1
. If you are interested in this function and python scoping rules, please refer to:
Finding The Minimum x
Such That f(x) = true
We want to construct a function firstTrue
such that firstTrue(lo, hi, f)
returns the first x
in the range [lo,hi]
such that f(x) = true
. If no such x
exists, then firstTrue
should return hi+1
.
Similarly to the previous part, this can be done with binary search if f(x)
satisfies both of the following conditions:
- If
f(x)
is true, thenf(y)
is true for all . - If
f(x)
is false, thenf(y)
is false for all .
We will need to do the same thing, but when the condition is satisfied, we will cut the right part, and when it's not, the left part will be cut.
C++
#include <bits/stdc++.h>using namespace std;int firstTrue(int lo, int hi, function<bool(int)> f) {for (hi ++; lo < hi; ) {int mid = lo+(hi-lo)/2;if (f(mid)) hi = mid;else lo = mid+1;}return lo;
Java
import java.util.function.Predicate;public class BinarySearch {public static void main(String[] args) {System.out.println(firstTrue(2,10,(x) -> true)); // 2System.out.println(firstTrue(2,10,(x) -> x*x>=30)); // 6System.out.println(firstTrue(2,10,(x) -> false)); // 11}public static int firstTrue(int lo, int hi, Predicate<Integer> f) {
Python
def firstTrue(lo, hi, f):# returns smallest x in [lo,hi] that satisfies f# hi+1 if no x satisfies fhi+=1while lo < hi:mid = (lo+hi)//2if f(mid):hi = midelse:lo = mid + 1return loprint(firstTrue(2, 10, lambda x : True)) # 2print(firstTrue(2, 10, lambda x : x * x >= 30)) # 6print(firstTrue(2, 10, lambda x : False)) # 11
Example - Maximum Median
Focus Problem – read through this problem before continuing!
Statement: Given an array of integers, where is odd, we can perform the following operation on it times: take any element of the array and increase it by . We want to make the median of the array as large as possible after operations.
Constraints: and is odd.
Solution
Common Mistakes
Mistake 1 - Off By One
Consider the code from CSAcademy's Binary Search on Functions.
C++
long long f(int x) {return (long long)x * x;}int square_root(int x) {int left = 0, right = x;while (left < right) {int middle = (left + right) / 2;if (f(middle) <= x) {left = middle;} else {
Java
public static long f(int x) {return (long)x * x;}public static int square_root(int x) {int left = 0, right = x;while (left < right) {int middle = (left + right) / 2;if (f(middle) <= x) {left = middle;} else {
Python
def f(x):return x*xdef square_root(x):left = 0right = 0while left < right:middle = (left + right) // 2if f(middle) <= x:left = middleelse:right = middle - 1return left
This results in an infinite loop if left=0
and right=1
! To fix this, set middle = (left+right+1)/2
instead.
Mistake 2 - Not Accounting for Negative Bounds
Consider a slightly modified version of firstTrue
:
C++
int firstTrue(int lo, int hi, function<bool(int)> f) {for (hi ++; lo < hi; ) {int mid = (lo+hi)/2;if (f(mid)) hi = mid;else lo = mid+1;}return lo;}
Java
public static int firstTrue(int lo, int hi, Predicate<Integer> f) {for (hi ++; lo < hi; ) {int mid = (lo+hi)/2;if (f.test(mid)) hi = mid;else lo = mid+1;}return lo;}
Python
def firstTrue(lo, hi, f):hi+=1while lo < hi:mid = (lo+hi)//2if f(mid):hi = midelse:lo = mid+1return lo
This code does not necessarily work if lo
is negative! Consider the following example:
C++
int main() {cout << firstTrue(-10,-10,[](int x) { return false; }) << "\n";// -8, should be -9cout << firstTrue(-10,-10,[](int x) { return true; }) << "\n";// infinite loop}
Java
public static void main(String[] args) {System.out.println(firstTrue(-10,-10,(x) -> false));// -8, should be -9System.out.println(firstTrue(-10,-10,(x) -> true));// infinite loop}
Python
print(firstTrue(-10,-10,lambda x : False))# -8, should be -9print(firstTrue(-10,-10,lambda x : True))# infinite loop
This is because dividing an odd negative integer by two will round it up instead of down.
C++
int firstTrue(int lo, int hi, function<bool(int)> f) {for (hi ++; lo < hi; ) {int mid = lo+(hi-lo)/2;if f(mid) hi = mid;else lo = mid+1;}return lo;}
Java
public static int firstTrue(int lo, int hi, Predicate<Integer> f) {for (hi ++; lo < hi; ) {int mid = lo+(hi-lo)/2;if(f.test(mid)) hi = mid;else lo = mid+1;}return lo;}
Python
def firstTrue(lo, hi, f):hi+=1while lo < hi:mid = lo+(hi-lo)/2if f(mid):hi = midelse:lo = mid+1return lo
Mistake 3 - Integer Overflow
The first version of firstTrue
won't work if hi-lo
initially exceeds INT_MAX
, while the second version of firstTrue
won't work if lo+hi
exceeds INT_MAX
at any point during execution. If this is an issue, use long long
s instead of int
s.
Library Functions For Binary Search
C++
Resources | |||
---|---|---|---|
CPP | with examples |
Java
Resources | |||
---|---|---|---|
JAVA | |||
JAVA |
Python
Resources | |||
---|---|---|---|
Python | |||
GFG |
Example - Counting Haybales
Focus Problem – read through this problem before continuing!
As each of the points are in the range , storing locations of haybales in a boolean array and then taking prefix sums of that would take too much time and memory.
Instead, let's place all of the locations of the haybales into a list and sort it. Now we can use binary search to count the number of cows in any range in time.
With Built-in Function
C++
We can use the the built-in lower_bound
and upper_bound
functions.
#include <bits/stdc++.h>using namespace std;using ll = long long;using vi = vector<int>;#define pb push_back#define rsz resize#define all(x) begin(x), end(x)#define sz(x) (int)(x).size()
Java
We can use the builtin Arrays.binarySearch
function.
import java.io.*;import java.util.*;public class haybales{public static void main(String[] args) throws IOException{BufferedReader br = new BufferedReader(new FileReader(new File("haybales.in")));PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("haybales.out")));StringTokenizer st = new StringTokenizer(br.readLine());
Python
We can use the builtin bisect.bisect
function.
from bisect import bisectinp = open("haybales.in", 'r')out = open("haybales.out", 'w')N, Q = map(int, inp.readline().split())arr = sorted(list(map(int, inp.readline().split())))for i in range(Q):
Without Built-in Function
C++
#include <bits/stdc++.h>using namespace std;using ll = long long;using vi = vector<int>;#define pb push_back#define rsz resize#define all(x) begin(x), end(x)#define sz(x) (int)(x).size()
Java
import java.util.*;import java.io.*;public class Haybales{static int N, Q;public static void main(String[] args) throws IOException{Kattio io = new Kattio("haybales");N = io.nextInt();Q = io.nextInt();List<Integer> v = new ArrayList<>();
Problems
USACO
Status | Source | Problem Name | Difficulty | Tags | Solution | URL |
---|---|---|---|---|---|---|
Silver | Easy | Show TagsBinary Search, Ordered Set | ||||
Silver | Easy | Show TagsBinary Search, Sorting | ||||
Silver | Easy | Show TagsBinary Search, Sorting | ||||
Silver | Normal | Show TagsBinary Search, Sorting | ||||
Gold | Hard | Show TagsBinary Search, Sorting | External Sol | |||
Silver | Very Hard | Show TagsBinary Search, Sqrt | External Sol | |||
Plat | Insane | Show TagsBinary Search, Sorting |
General
Module Progress:
Join the USACO Forum!
Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!