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_Bronze/Complete_Search.mdx
2020-07-13 12:19:05 -04:00

215 lines
11 KiB
Text

---
id: complete-search
title: "Complete Search"
author: Darren Yao
description: In many problems (especially in Bronze) it suffices to check all possible cases in the solution space.
frequency: 4
---
import { Problem } from "../models";
export const metadata = {
problems: {
easier: [
new Problem("CSES", "Apple Division", "1623", "Intro|Very Easy", false, ["Recursion"], "all $2^n$ subsets"),
new Problem("Bronze", "Diamond Collector", "639", "Easy", false, ["Nested Loop"], "fix the min"), // 99.9
new Problem("Bronze", "Milk Pails", "615", "Easy", false, ["Nested Loop"], "Hint: Fix the number of first-type operations you perform."), // 99.71
new Problem("Bronze", "Bovine Genomics", "736", "Easy", false, ["Nested Loop"], ""), // 99.73
new Problem("Bronze", "Cow Gymnastics", "963", "Easy", false, ["Nested Loop"], "Hint: Brute force over all possible pairs."), // 99.93
new Problem("Bronze", "Where Am I?", "964", "Easy", false, [], "Hint: Brute force over all possible substrings."), // 99.48
new Problem("Bronze", "Triangles", "1011", "Easy", false, [], "loop over the possible right angles"), // 98.02
],
harder: [
new Problem("CSES", "Chessboard and Queens", "1624", "Normal", false, [], "backtracking"),
new Problem("Bronze", "Lifeguards", "784", "Normal", false, [], "Hint: Try removing each lifeguard one at a time."), // 98.76
new Problem("Bronze", "Photoshoot", "988", "Normal", false, [], "Hint: Figure out what exactly you're complete searching."), // 98.45
new Problem("Bronze", "Back & Forth", "857", "Hard", false, [], "exponential time going through all possibilities"), // 99.63
new Problem("Bronze", "Field Reduction", "641", "Hard", false, [], "Hint: For this problem, you can't do a full complete search; you have to do a reduced search."), // 91.75
new Problem("Bronze", "Circle Cross", "712", "Hard", false, [], ""), // 89.6
new Problem("Silver", "Bovine Genomics", "739", "Hard", false, [], ""),
new Problem("Bronze", "Load Balancing", "617", "Very Hard", false, [], ""), // 82.79
new Problem("Bronze", "Bull in a China Shop", "640", "Very Hard", false, [], "lots of WA on this one"), // 47.38
new Problem("Silver", "Field Reduction", "642", "Very Hard", false, [], ""),
],
permSam: [
new Problem("CSES", "Creating Strings I", "1622", "Intro|Easy", false, [], "all perms of string"),
],
perm: [
new Problem("Bronze", "Livestock Lineup", "965", "Hard", false, ["permutations"], ""), // 91.95
]
}
};
<resources>
<resource source="CPH" title="5.1 to 5.3 - Complete Search"> </resource>
</resources>
<br />
In many problems (especially in Bronze) it suffices to check all possible cases in the solution space, whether it be all elements, all pairs of elements, or all subsets, or all permutations. Unsurprisingly, this is called **complete search** (or **brute force**), because it completely searches the entire solution space.
## Example
### Statement
You are given $N$ $(3 \leq N \leq 5000)$ integer points on the coordinate plane. Find the square of the maximum Euclidean distance (aka length of the straight line) between any two of the points.
#### Input Format
The first line contains an integer $N$.
The second line contains $N$ integers, the $x$-coordinates of the points: $x_1, x_2, \dots, x_N$ ($-1000 \leq x_i \leq 1000$).
The third line contains $N$ integers, the $y$-coordinates of the points: $y_1, y_2, \dots, y_N$ ($-1000 \leq y_i \leq 1000$).
#### Output Format
Print one integer, the square of the maximum Euclidean distance between any two of the points.
<spoiler title="Solution">
We can brute-force every pair of points and find the square of the distance between them, by squaring the formula for Euclidean distance: $\text{distance}^2 = (x_2-x_1)^2 + (y_2-y_1)^2$. Thus, we store the coordinates in arrays `X[]` and `Y[]`, such that `X[i]` and `Y[i]` are the $x$- and $y$-coordinates of the $i_{th}$ point, respectively. Then, we iterate through all possible pairs of points, using a variable max to store the maximum square of distance between any pair seen so far, and if the square of the distance between a pair is greater than our current maximum, we set our current maximum to it.
```java
int max = 0; // storing the current maximum
for(int i = 0; i < n; i++){ // for each first point
for(int j = i+1; j < n; j++){ // for each second point
int dx = x[i] - x[j];
int dy = y[i] - y[j];
max = Math.max(max, dx*dx + dy*dy);
// if the square of the distance between the two points is greater than
// our current maximum, then update the maximum
}
}
pw.println(max);
```
```cpp
int high = 0; // storing the current maximum
for(int i = 0; i < n; i++){ // for each first point
for(int j = i+1; j < n; j++){ // for each second point
int dx = x[i] - x[j];
int dy = y[i] - y[j];
high = max(high, dx*dx + dy*dy);
// if the square of the distance between the two points is greater than
// our current maximum, then update the maximum
}
}
cout << high << endl;
```
A couple notes:
- First, since we're iterating through all pairs of points, we start the $j$ loop from $j = i+1$ so that point $i$ and point $j$ are never the same point. Furthermore, it makes it so that each pair is only counted once. In this problem, it doesn't matter whether we double-count pairs or whether we allow $i$ and $j$ to be the same point, but in other problems where we're counting something rather than looking at the maximum, it's important to be careful that we don't overcount.
- Secondly, the problem asks for the square of the maximum Euclidean distance between any two points. Some students may be tempted to maintain the maximum distance in an integer variable, and then square it at the end when outputting. However, the problem here is that while the square of the distance between two integer points is always an integer, the distance itself isn't guaranteed to be an integer. Thus, we'll end up shoving a non-integer value into an integer variable, which truncates the decimal part. Using a floating point variable will probably work, but you should generally stay with integers whenever possible.
</spoiler>
## Problems
### Easier
<problems-list problems={metadata.problems.easier} />
### Harder
<problems-list problems={metadata.problems.harder} />
## Generating Permutations
<resources>
<resource source="GFG" url="write-a-c-program-to-print-all-permutations-of-a-given-string"> </resource>
</resources>
<problems-list problems={metadata.problems.permSam} />
A **permutation** is a reordering of a list of elements. Some problems will ask for an ordering of elements that satisfies certain conditions. In these problems, if $N \leq 10$, we can probably iterate through all permutations and check each permutation for validity. For a list of $N$ elements, there are $N!$ ways to permute them, and generally we'll need to read through each permutation once to check its validity, for a time complexity of $O(N \cdot N!)$.
### Java
We'll have to implement this ourselves, which is called [Heap's Algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm) (no relation to the heap data structure). What's going to be in the check function depends on the problem, but it should verify whether the current permutation satisfies the constraints given in the problem.
As an example, here are the permutations generated by Heap's Algorithm for $[1, 2, 3]$:
$$
[1, 2, 3], [2, 1, 3], [3, 1, 2], [1, 3, 2], [2, 3, 1], [3, 2, 1]
$$
Code for iterating over all permutations is as follows:
```java
// this method is called with k equal to the length of arr
static void generate(int[] arr, int k){
if(k == 1){
check(arr); // check the current permutation for validity
} else {
generate(arr, k-1);
for(int i = 0; i < k-1; i++){
if(k % 2 == 0){
swap(arr, i, k-1);
// swap indices i and k-1 of arr
} else {
swap(arr, 0, k-1);
// swap indices 0 and k-1 of arr
}
generate(arr, k-1);
}
}
}
```
However, the correctness of this code is not immediately apparent, and it does not generate permutations in lexicographical order (but the following does):
```java
import java.util.*;
public class Test {
static boolean[] used;
static ArrayList<Integer> cur = new ArrayList<Integer>();
static int n;
static void gen() {
if (cur.size() == n) {
System.out.println(cur); // print permutation
return;
}
for (int i = 0; i < n; ++i) if (!used[i]) {
used[i] = true; cur.add(i+1);
gen();
used[i] = false; cur.remove(cur.size()-1);
}
}
static void genPerm(int _n) {
n = _n; used = new boolean[n];
gen();
}
public static void main(String[] Args) {
genPerm(5);
}
}
```
### C++
We can just use the `next_permutation()` function. This function takes in a range and modifies it to the next greater permutation. If there is no greater permutation, it returns false. To iterate through all permutations, place it inside a `do-while` loop. We are using a `do-while` loop here instead of a typical `while` loop because a `while` loop would modify the smallest permutation before we got a chance to process it.
```cpp
do {
check(v); // process or check the current permutation for validity
} while(next_permutation(v.begin(), v.end()));
```
Note that this generates the permutations in **lexicographical order**.
<spoiler title="What's lexicographical order?">
Think about how are words ordered in a dictionary. (In fact, this is where the term "lexicographical" comes from.)
In dictionaries, you will see that words beginning with the letter 'a' appears at the very beginning, followed by words beginning with 'b', and so on. If two words have the same starting letter, the second letter is used to compare them; if both the first and second letters are the same, then use the third letter to compare them, and so on until we either reach a letter that is different, or we reach the end of some word (in this case, the shorter word goes first).
Permutations can be placed into lexicographical order in almost the same way. We first group permutations by their first element; if the first element of two permutations are equal, then we compare them by the second element; if the second element is also equal, then we compare by the third element, and so on.
For example, the permutations of 3 elements, in lexicographical order, are [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], and [3, 2, 1]. Notice that the list starts with permutations beginning with 1 (just like a dictionary that starts with words beginning with 'a'), followed by those beginning with 2 and those beginning with 3. Within the same starting element, the second element is used to make comparisions.
Generally, unless you are specifically asked to find the lexicographically smallest/largest solution, you do not need to worry about whether permutations are being generated in lexicographical order. However, the idea of lexicographical order does appear quite often in programming contest problems, and in a variety of contexts, so it is strongly recommended that you familiarize yourself with its definition.
</spoiler>
### Problems
<problems-list problems={metadata.problems.perm} />