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 Using Built-in FunctionsProblemsUSACOGeneralQuizBinary Search
Authors: Darren Yao, Abutalib Namazov, Andrew Wang, Qi Wang, Dustin Miao
Binary searching on arbitrary monotonic functions and built-in functions for binary search.
Prerequisites
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 Using Built-in FunctionsProblemsUSACOGeneralQuizIntroduction
Resources | ||||
---|---|---|---|---|
CSA | animation, code, lower_bound + upper_bound | |||
CPH | code, lower_bound + upper_bound, some applications | |||
CF EDU | 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.
x
Such That f(x) = true
Finding The Maximum 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 last_true(int lo, int hi, function<bool(int)> f) {// if none of the values in the range work, return lo - 1lo--;while (lo < hi) {// find the middle of the current range (rounding up)int mid = lo + (hi - lo + 1) / 2;if (f(mid)) {
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) {// all numbers satisfy the condition (outputs 10)System.out.println(lastTrue(2, 10, (x) -> true));// outputs 5System.out.println(lastTrue(2, 10, (x) -> x * x <= 30));
See the
Java Predicate Interface
if you're not familiar with the Predicate
interface used.
Python
from typing import Callabledef last_true(lo: int, hi: int, f: Callable[[int], bool]) -> int:"""Binary Search:param lo: lower bound:param hi: upper bound:param f: a function that returns whether a number is valid or not:return: the maximum x such that f(x) is true
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++
#include <bits/stdc++.h>using namespace std;int last_true(int lo, int hi, function<bool(int)> f) {lo--;for (int dif = hi - lo; dif > 0; 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) {lo--;for (int dif = hi - lo; dif > 0; dif /= 2) {while (lo + dif <= hi && f.test(lo + dif)) { lo += dif; }}return lo;}
Python
from typing import Callabledef last_true(lo: int, hi: int, f: Callable[[int], bool]) -> int:"""Binary Search:param lo: lower bound:param hi: upper bound:param f: a function that returns whether a number is valid or not:return: the maximum x such that f(x) is true
x
Such That f(x) = true
Finding The Minimum 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 first_true(int lo, int hi, function<bool(int)> f) {hi++;while (lo < hi) {int mid = lo + (hi - lo) / 2;if (f(mid)) {hi = mid;} else {
Java
import java.util.function.Predicate;public class BinarySearch {public static void main(String[] args) {System.out.println(firstTrue(2, 10, (x) -> true)); // outputs 2System.out.println(firstTrue(2, 10, (x) -> x * x >= 30)); // outputs 6System.out.println(firstTrue(2, 10, (x) -> false)); // outputs 11}public static int firstTrue(int lo, int hi, Predicate<Integer> f) {
Python
from typing import Callabledef first_true(lo: int, hi: int, f: Callable[[int], bool]) -> int:hi += 1while lo < hi:mid = (lo + hi) // 2if f(mid):hi = midelse:
Example - Maximum Median
Focus Problem – try your best to solve 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.
To get the current median, we first sort the array in ascending order.
Now, notice that to increase the current value to a value, say . All values currently greater than or equal to the median, must remain greater than or equal to the median.
For example, say we have the sorted array . The current median is , so to increase the median to , we have to increase the current median by and we also have to increase the 's to .
Following this idea, to increase the median to , we need to increase all values in the second half of the array to some value greater than or equal to .
Then, we binary search for the maximum possible median. We know that the number of operations required to raise the median to increases monotonically as increases, so we can use binary search. For a given median value , the number of operations required to raise the median to is
If this value is less than or equal to , then can be the median, so our
check function returns true
. Otherwise, cannot be the median, so our check
function returns false
.
C++
#include <bits/stdc++.h>using namespace std;int last_true(int lo, int hi, function<bool(int)> f) {lo--;while (lo < hi) {int mid = lo + (hi - lo + 1) / 2;if (f(mid)) {lo = mid;} else {
Here we write the check function as a lambda expression.
Java
import java.io.*;import java.util.*;public class MaxMed {static int size;static int maxOps;static int[] arr;public static void main(String[] args) {Kattio io = new Kattio();
Python
from typing import Callabledef med_reachable(x: int) -> bool:"""Checks whether the number of given operations is sufficientto raise the median of the array to x."""ops_needed = 0for i in range((size - 1) // 2, size):
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 sqrt(int x) {int lo = 0;int hi = x;while (lo < hi) {int mid = (lo + hi) / 2;if (f(mid) <= x) {lo = mid;} else {hi = mid - 1;}}return lo;}
Java
public static long f(int x) { return (long)x * x; }public static int sqrt(int x) {int lo = 0;int hi = x;while (lo < hi) {int mid = (lo + hi) / 2;if (f(mid) <= x) {lo = mid;} else {hi = mid - 1;}}return lo;}
Python
def f(x: int) -> int:return x * xdef sqrt(x: int) -> int:lo = 0hi = 0while lo < hi:mid = (lo + hi) // 2if f(mid) <= x:lo = midelse:hi = mid - 1return lo
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++
#include <functional>using namespace std;int first_true(int lo, int hi, function<bool(int)> f) {hi++;while (lo < hi) {int mid = (lo + hi) / 2;if (f(mid)) {hi = mid;
Java
public static int firstTrue(int lo, int hi, Predicate<Integer> f) {hi++;while (lo < hi) {int mid = (lo + hi) / 2;if (f.test(mid)) {hi = mid;} else {lo = mid + 1;}}return lo;}
Python
from typing import Callabledef first_true(lo: int, hi: int, f: Callable[[int], bool]) -> int: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() {// outputs -8 instead of -9cout << first_true(-10, -10, [](int x) { return false; }) << "\n";// causes an infinite loopcout << first_true(-10, -10, [](int x) { return true; }) << "\n";}
Java
public static void main(String[] args) {// outputs -8 instead of -9System.out.println(firstTrue(-10, -10, (x) -> false));// causes an infinite loopSystem.out.println(firstTrue(-10, -10, (x) -> true));}
Python
# outputs -8 instead of -9print(first_true(-10, -10, lambda x: False))# causes an infinite loopprint(first_true(-10, -10, lambda x: True))
This is because dividing an odd negative integer by two will round it up instead of down.
C++
#include <functional>using namespace std;int first_true(int lo, int hi, function<bool(int)> f) {hi++;while (lo < hi) {int mid = lo + (hi - lo) / 2;if (f(mid)) {hi = mid;
Java
public static int firstTrue(int lo, int hi, Predicate<Integer> f) {hi++;while (lo < hi) {int mid = lo + (hi - lo) / 2;if (f.test(mid)) {hi = mid;} else {lo = mid + 1;}}return lo;}
Python
from typing import Callabledef first_true(lo: int, hi: int, f: Callable[[int], bool]) -> int: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 – try your best to solve this problem before continuing!
View Internal SolutionAs 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;void setIO(string name = "") { // name is nonempty for USACO file I/Oios_base::sync_with_stdio(0);cin.tie(0); // see Fast Input & Output// alternatively, cin.tie(0)->sync_with_stdio(0);if (!name.empty()) {freopen((name + ".in").c_str(), "r", stdin); // see Input & Outputfreopen((name + ".out").c_str(), "w", stdout);
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("haybales.out");StringTokenizer st = new StringTokenizer(br.readLine());int baleNum = Integer.parseInt(st.nextToken());
Python
We can use the builtin
bisect.bisect
function.
from bisect import bisectinp = open("haybales.in", "r")out = open("haybales.out", "w")bale_num, query_num = map(int, inp.readline().split())bales = sorted(list(map(int, inp.readline().split())))for _ in range(query_num):start, end = map(int, inp.readline().split())print(bisect(bales, end) - bisect(bales, start - 1), file=out)
Without Using Built-in Functions
C++
#include <bits/stdc++.h>using namespace std;void setIO(string name = "") { // name is nonempty for USACO file I/Oios_base::sync_with_stdio(0);cin.tie(0); // see Fast Input & Output// alternatively, cin.tie(0)->sync_with_stdio(0);if (!name.empty()) {freopen((name + ".in").c_str(), "r", stdin); // see Input & Outputfreopen((name + ".out").c_str(), "w", stdout);
Java
import java.io.*;import java.util.*;public class Haybales {static int[] bales;public static void main(String[] args) throws IOException {Kattio io = new Kattio("haybales");int baleNum = io.nextInt();int queryNum = io.nextInt();bales = new int[baleNum];
Python
def at_most(x: int) -> int:lo = 0hi = len(bales)while lo < hi:mid = (lo + hi) // 2if bales[mid] <= x:lo = mid + 1else:hi = midreturn lo
Problems
USACO
Status | Source | Problem Name | Difficulty | Tags | |
---|---|---|---|---|---|
Silver | Easy | Show TagsBinary Search, Sorted Set | |||
Silver | Easy | Show TagsBinary Search, Sorting | |||
Silver | Easy | Show TagsBinary Search, Sorting | |||
Silver | Normal | Show TagsBinary Search, Sorting | |||
Silver | Hard | Show TagsBinary Search | |||
Gold | Hard | Show Tags2P, Binary Search, Greedy, Sorting | |||
Silver | Very Hard | Show TagsBinary Search, Sqrt | |||
Platinum | Insane | Show TagsBinary Search, Sorting |
General
Status | Source | Problem Name | Difficulty | Tags | |
---|---|---|---|---|---|
CF | Easy | Show TagsBinary Search | |||
CSES | Easy | Show TagsBinary Search | |||
CSES | Easy | Show TagsBinary Search | |||
CF | Easy | Show TagsBinary Search | |||
CC | Normal | Show Tags2D Prefix Sums, Binary Search | |||
CSES | Normal | Show TagsBinary Search | |||
CF | Normal | Show TagsBinary Search, Prefix Sums | |||
CF | Normal | Show TagsBinary Search | |||
CF | Normal | Show TagsBinary Search | |||
CF | Normal | Show TagsBinary Search, Greedy, Prefix Sums | |||
CF | Normal | Show TagsBinary Search, Prefix Sums | |||
AC | Hard | Show TagsBinary Search, Prefix Sums, Sorting | |||
CF | Hard | Show TagsBinary Search, Prefix Sums | |||
CF | Hard | Show TagsBinary Search, Priority Queue | |||
CF | Hard | Show TagsBinary Search | |||
CF | Hard | Show TagsBinary Search | |||
Baltic OI | Very Hard | Show TagsBinary Search, Stack |
Quiz
What are the worst and best case time complexities for searching for a number in a sorted array of size with the following code?
C++
bool binary_search(int x, std::vector<int> &a) {int l = 0;int r = a.size() - 1;while (l < r) {int m = (l + r) / 2;if (a[m] == x) {return true;} else if (x > a[m]) {l = m + 1;} else {r = m - 1;}}return false;}
Java
boolean binary_search(int x, int[] a) {int l = 0;int r = a.length - 1;while (l < r) {int m = (l + r) / 2;if (a[m] == x) {return true;} else if (x > a[m]) {l = m + 1;} else {r = m - 1;}}return false;}
Python
def binary_search(x: int, a: list):l = 0r = len(a) - 1while l < r:m = (l + r) // 2if a[m] == x:return Trueelif x > a[m]:l = m + 1else:r = m - 1return False
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!