This repository has been archived on 2022-06-22. You can view files and clone it, but cannot push or open issues or pull requests.
usaco-guide/content/3_Silver/Binary_Search_Ans.mdx

267 lines
10 KiB
Text

---
id: binary-search-ans
title: "Binary Search on the Answer"
author: Darren Yao, Abutalib Namazov
prerequisites:
- binary-search-sorted
description: "An extension on binary search to search beyond an array, and rather through possible answers."
frequency: 3
---
import { Problem } from "../models";
export const problems = {
ex: [
new Problem("CF", "Div 2 C - Maximum Median", "contest/1201/problem/C", "Easy", false, [], ""),
],
usaco: [
new Problem("Silver", "Moo Buzz", "966", "Very 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("Gold", "High Card Low Card", "573", "Normal", false, [], ""),
new Problem("Silver", "Loan Repayment", "991", "Hard", false, [], "requires some rather tricky analysis to speed up naive $O(N\log N)$ solution"),
new Problem("Gold", "Angry Cows", "597", "Hard", false, [], ""),
],
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"),
],
};
<Resources>
<Resource source="IUSACO" title="12 - Binary Search">module is based off this</Resource>
</Resources>
<br />
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
}
```
(todo: remove one of these)
<IncompleteSection />
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.
<LanguageSection>
<CPPSection>
```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;
}
```
</CPPSection>
<JavaSection>
```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;
}
```
</JavaSection>
</LanguageSection>
## Example: Maximum Median
<Problems problems={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.
<LanguageSection>
<CPPSection>
```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';
}
```
</CPPSection>
<JavaSection>
```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; }
}
```
</JavaSection>
</LanguageSection>
## USACO Problems
<Problems problems={problems.usaco} />
## General Problems
<Problems problems={problems.general} />