229 lines
9.6 KiB
Text
229 lines
9.6 KiB
Text
---
|
|
id: binary-search-ans
|
|
title: "Binary Search on the Answer"
|
|
author: Darren Yao, Abutalib Namazov
|
|
prerequisites:
|
|
- Silver - Binary Search on a Sorted Array
|
|
description: You should already be familiar with the concept of binary searching for a number in a sorted array. However, binary search can be extended to binary searching on the answer itself.
|
|
frequency: 3
|
|
---
|
|
|
|
import { Problem } from "../models";
|
|
|
|
export const metadata = {
|
|
problems: {
|
|
ex: [
|
|
new Problem("CF", "Div 2 C - Maximum Median", "contest/1201/problem/C", "Easy", false, [], ""),
|
|
],
|
|
usaco: [
|
|
new Problem("Silver", "Moo Buzz", "966", "Easy", false, [], "binary search not required"),
|
|
new Problem("Silver", "Cow Dance Show", "690", "Easy", false, [], "binary search on $K$ and simulate"),
|
|
new Problem("Silver", "Convention", "858", "Easy", false, [], "determine whether $M$ buses suffice if every cow waits at most $T$ minutes, use a greedy strategy (applies to next two problems as well)"),
|
|
new Problem("Silver", "Angry Cows", "594", "Easy", false, [], "check in $O(N)$ how many haybales you can destroy with fixed radius $R$"),
|
|
new Problem("Silver", "Social Distancing", "1038", "Normal", false, [], "check in $O(N+M)$ how many cows you can place with distance $D$"),
|
|
new Problem("Silver", "Loan Repayment", "991", "Hard", false, [], "requires some rather tricky analysis to speed up naive $O(N\log N)$ solution"),
|
|
],
|
|
general: [
|
|
new Problem("CSES", "Factory Machines", "1620", "Easy", false, [], "binary search on time and calculate the total products for this time"),
|
|
new Problem("CSES", "Array Division", "1085", "Easy", false, [], ""),
|
|
new Problem("CF", "Edu C: Magic Ship", "problemset/problem/1117/C", "Normal", false, ["Binary Search", "Prefix Sums"], "binary search on number of the days and then counting full cycles and extra days with prefix sums"),
|
|
new Problem("CF", "The Meeting Place Cannot Be Changed", "contest/782/problem/B", "Normal", false, [], "binary search on time with epsilon and then find if there exist non-empty intersection of the ranges (maximum of left points and minimum of right points)"),
|
|
new Problem("CF", "Preparing for Merge Sort", "contest/847/problem/B", "Normal", false, [], "process numbers from left to right, keep track of the last element of each block and use binary search of lower_bound to find which block the current numbers belongs"),
|
|
new Problem("CF", "Office Keys", "problemset/problem/830/A", "Normal", false, [], "notice that people will take a subarray of length n of keys. so it's possible to fix one position and binary search on another."),
|
|
new Problem("CF", "Level Generation", "problemset/problem/818/F", "Hard", false, [], "first find out which is the best way to construct the graph, then it's possible to see that the number of edges increase for some range and then decrease; so, using binary search find the last i such that f(i-1)<=f(i)"),
|
|
new Problem("CF", "Packmen", "contest/847/problem/E", "Hard", false, [], "binary search on time and check if packmen can eat all keeping left and right endpoints"),
|
|
],
|
|
}
|
|
};
|
|
|
|
|
|
|
|
When we binary search on an answer, we start with a search space of size $N$ which we know the answer lies in. Then each iteration of the binary search cuts the search space in half, so the algorithm tests $O(\log N)$ values. This is efficient and much better than testing each possible value in the search space.
|
|
|
|
Let's say we have a function `check(x)` that returns `true` if the answer of $x$ is possible, and `false` otherwise. Usually, in such problems, we want to find the maximum or minimum value of $x$ such that `check(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](https://en.wikipedia.org/wiki/Monotonic_function), meaning that it is always non-decreasing or always non-increasing.
|
|
|
|
In particular, if we want to find the maximum `x` such that `check(x)` is true, then we can binary search if `check(x)` satisfies both of the following conditions:
|
|
|
|
- If `check(x)` is `true`, then `check(y)` is true for all $y \leq x$.
|
|
- If `check(x)` is `false`, then `check(y)` is false for all $y \geq x$.
|
|
|
|
For example, the last point at which the condition below is satisfied is 5.
|
|
|
|
```
|
|
check(1) = true
|
|
check(2) = true
|
|
check(3) = true
|
|
check(4) = true
|
|
check(5) = true
|
|
check(6) = false
|
|
check(7) = false
|
|
check(8) = false
|
|
```
|
|
|
|
If instead we're looking for the minimum `x` that satisfies some condition, then we can binary search if `check(x)` satisfies both of the following conditions:
|
|
|
|
- If `check(x)` is true, then `check(y)` is true for all $y \geq x$.
|
|
- If `check(x)` is false, then `check(y)` is false for all $y \leq x$.
|
|
|
|
The binary search function for this is very similar. 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.
|
|
|
|
Below, we present several algorithms for binary search, which search for the maximum `x` such that `check(x)` is true.
|
|
|
|
```cpp
|
|
int l = range_start, r = range_end, res = -1;
|
|
while(l <= r) {
|
|
int mid = (l + r) / 2; // find the middle of the current range
|
|
if (check(mid) == true) {
|
|
// if mid works, then all numbers smaller than mid also work
|
|
// so we only care about the part after mid
|
|
res = mid; // update the answer
|
|
l = mid + 1; // cut the part before mid
|
|
// notice that we already handled mid itself so we cut it as well
|
|
} else {
|
|
// if mid does not work, greater values would not work too
|
|
// so we don't care about them
|
|
r = mid - 1; // cut mid and after
|
|
}
|
|
}
|
|
// now res is the answer
|
|
// if res is still -1 that means no value in the range satisfies the condition
|
|
```
|
|
|
|
Here is a second implementation from Benq:
|
|
|
|
```cpp
|
|
int fstTrue(function<bool(int)> f, int lo, int hi) {
|
|
hi ++; assert(lo <= hi); // assuming f is increasing
|
|
while (lo < hi) { // find first index such that f is true
|
|
int mid = (lo+hi)/2; // returns hi+1 if no index exists
|
|
f(mid) ? hi = mid : lo = mid+1;
|
|
}
|
|
return lo;
|
|
}
|
|
|
|
int main() {
|
|
cout << fstTrue([](int x) { return 1; },2,10)) << "\n"; // 2
|
|
cout << fstTrue([](int x) { return x >= 5; },2,10)) << "\n"; // 5
|
|
cout << fstTrue([](int x) { return x >= 20; },2,10)) << "\n"; // 11
|
|
}
|
|
```
|
|
|
|
There is also another approach to binary searching 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.
|
|
```cpp
|
|
long long search(){
|
|
long long pos = 0; long long max = 2E9;
|
|
for(long long a = max; a >= 1; a /= 2){
|
|
while(check(pos+a)) pos += a;
|
|
}
|
|
return pos;
|
|
}
|
|
```
|
|
```java
|
|
static long search(){
|
|
long pos = 0; long max = (long)2E9;
|
|
for(long a = max; a >= 1; a /= 2){
|
|
while(check(pos+a)) pos += a;
|
|
}
|
|
return pos;
|
|
}
|
|
```
|
|
## Example: Maximum Median
|
|
|
|
<problems-list problems={metadata.problems.ex} />
|
|
|
|
**Statement:** Given an array $\texttt{arr}$ of $n$ integers, where $n$ is odd, we can perform the following operation on it $k$ times: take any element of the array and increase it by $1$. We want to make the median of the array as large as possible after $k$ operations.
|
|
|
|
**Constraints:** $1 \leq n \leq 2 \cdot 10^5, 1 \leq k \leq 10^9$ and $n$ is odd.
|
|
|
|
**Solution:** We first sort the array in ascending order. Then, we binary search for the maximum possible median. We know that the number of operations required to raise the median to $x$ increases monotonically as $x$ increases, so we can use binary search. For a given median value $x$, the number of operations required to raise the median to $x$ is
|
|
|
|
$$
|
|
\sum_{i=(n+1)/2}^{n} \max(0, x - \texttt{arr}[i]).
|
|
$$
|
|
|
|
If this value is less than or equal to $k$, then $x$ can be the median, so our check function returns `true`. Otherwise, $x$ cannot be the median, so our check function returns `false`.
|
|
|
|
The solution codes use the jump implementation of binary search.
|
|
|
|
```cpp
|
|
int n;
|
|
long long k;
|
|
vector<long long> v;
|
|
|
|
// checks whether the number of given operations is sufficient
|
|
// to raise the median of the array to x
|
|
bool check(long long x){
|
|
long long operationsNeeded = 0;
|
|
for(int i = (n-1)/2; i < n; i++){
|
|
operationsNeeded += max(0, x-v[i]);
|
|
}
|
|
if(operationsNeeded <= k) return true;
|
|
else return false;
|
|
}
|
|
|
|
// binary searches for the correct answer
|
|
long long search(){
|
|
long long pos = 0; long long max = 2E9;
|
|
for(long long a = max; a >= 1; a /= 2){
|
|
while(check(pos+a)) pos += a;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
int main() {
|
|
cin >> n >> k;
|
|
for(int i = 0; i < n; i++){
|
|
int t;
|
|
cin >> t;
|
|
v.push_back(t);
|
|
}
|
|
sort(v.begin(), v.end());
|
|
|
|
cout << search() << '\n';
|
|
}
|
|
```
|
|
```java
|
|
static int n;
|
|
static long k;
|
|
static long[] arr;
|
|
public static void main(String[] args) {
|
|
|
|
n = r.nextInt(); k = r.nextLong();
|
|
arr = new long[n];
|
|
for(int i = 0; i < n; i++){
|
|
arr[i] = r.nextLong();
|
|
}
|
|
Arrays.sort(arr);
|
|
|
|
pw.println(search());
|
|
pw.close();
|
|
}
|
|
|
|
// binary searches for the correct answer
|
|
static long search(){
|
|
long pos = 0; long max = (long)2E9;
|
|
for(long a = max; a >= 1; a /= 2){
|
|
while(check(pos+a)) pos += a;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
// checks whether the number of given operations is sufficient
|
|
// to raise the median of the array to x
|
|
static boolean check(long x){
|
|
long operationsNeeded = 0;
|
|
for(int i = (n-1)/2; i < n; i++){
|
|
operationsNeeded += Math.max(0, x-arr[i]);
|
|
}
|
|
if(operationsNeeded <= k){ return true; }
|
|
else{ return false; }
|
|
}
|
|
```
|
|
|
|
## USACO Problems
|
|
|
|
<problems-list problems={metadata.problems.usaco} />
|
|
|
|
## General Problems
|
|
|
|
<problems-list problems={metadata.problems.general} />
|