move intro-greedy to bronze

This commit is contained in:
Benjamin Qi 2020-07-17 16:38:28 -04:00
parent 5c4d474a65
commit 913896343e
5 changed files with 191 additions and 169 deletions

View file

@ -68,16 +68,16 @@ Vim is probably the easiest way to print syntax-highlighted code on Mac, see the
- Using `/usr/local/bin/subl` instead of `~/bin/subl` worked for me on OS X Mojave.
- [Package - Sublime Linter (GCC)](https://packagecontrol.io/packages/SublimeLinter-gcc)
- highlights compilation errors and warnings from `-Wall`
- [Package - Fast Olympic Coding](https://github.com/Jatana/FastOlympicCoding) (I don't use)
- test manager can be useful
- linting is covered by the above
- stress testing is covered in "Debugging"
- can't get debug to work
<!-- - [Editing Build Settings](https://stackoverflow.com/questions/23789410/how-to-edit-sublime-text-build-settings)
- no need to do this if you just use command line to compile & run -->
<!-- - [FastOlympicCoding Addon](https://github.com/Jatana/FastOlympicCoding)
- not that useful
- stress testing is covered in "Debugging"
- linting is covered by the above
- debugger only works with clang
- [Editing Build Settings](https://stackoverflow.com/questions/23789410/how-to-edit-sublime-text-build-settings)
- no need to do this if you just use command line to compile & run
-->
## On Linux

View file

@ -1,72 +0,0 @@
---
id: ad-hoc
title: Approaching Ad Hoc Problems
author: Michael Cao
description: "Tips on Ad Hoc problems (problems with no clear category) in the Bronze Division."
frequency: 2
---
import { Problem } from "../models";
export const metadata = {
problems: {
general: [
new Problem("Bronze", "Cow Tipping", "689", "Hard", false, [], "Cells in the last row and column can be toggled uniquely. Toggle the appropriate ones and then recurse to the rectangle in the previous row/column, and solve the same way."),
new Problem("Bronze", "Race", "989", "Very Hard", false, [], "Greedily increment/decrement Bessies speed to fit the conditions until her total distance exceeds K."),
],
greedyTutorial: [
new Problem("Bronze", "Mad Scientist", "1012", "Normal", false, [], ""),
],
}
};
Some bronze problems don't fall into any specific category, such as compete search or rectangle geometry, and often require more thought while also being easier to implement. Any problem that doesn't fall under any well-defined category can be labelled as **Ad Hoc**.
Since Ad Hoc problems don't fall under any specific category, they can be hard to generalize, so there is no fixed algorithm or idea to solving these problems. In this module, we'll go over some general tips that may be useful in approaching these problems and present some practice problems.
## Tips for Approaching Ad Hoc Problems
<ul>
<li> Draw lots of small cases to gain a better understanding of the problem. If you're having trouble debugging, draw more cases. If you don't know how to start with a problem, draw more cases. Whenever you don't know how to further approach a problem, you're probably missing an important observation, so draw more cases and make observations about properties of the problem. </li>
<li> Whenever you find an observation that seems useful, write it down! Writing down ideas lets you easily come back to them later, and makes sure you don't forget about ideas that could potentially be the solution. </li>
<li> Don't get stuck on any specific idea, unless you see an entire solution. Trying to complete search an Ad Hoc problem will just end up wasting a lot of your time in contest. </li>
<li> Try to approach the problem from a lot of different perspectives. Try to draw a visual depiction of the problem, mess around with formulas, or model it as a Graph Theory problem (see Graph Theory module). One of the most helpful things you can do when solving Ad Hoc problems is to keep trying ideas until you make progress. This is something you get better at as you do more problems.</li>
</ul>
These tips are helpful in solving Ad Hoc problems. However, in the end, the best way to get better at Ad Hoc problems (or any type of problems) is to do a lot of them. Try to solve as many practice problems below as you can, and click the solution sketch tab if you can't figure the solution out.
## Greedy Problems
One thing to note about Ad Hoc problems in the USACO Bronze division is that many of them, while difficult to categorize into specific algorithms or data structures, can be solved using **Greedy** techniques. While this idea will be covered more in future [modules](https://usaco-guide.netlify.app/silver/greedy), we'll introduce the general mindset in this section.
When approaching a greedy problem, we want to make argument about the structure of the solution of the problem. Specifically, that certain "greedy" actions, or actions that create the best possible solution at some local point in the algorithm, will lead to an optimal solution at the end.
Consider some possible greedy algorithms for the USACO Bronze problem "Mad Scientist."
<problem-list problems={metadata.problems.greedyTutorial} />
<spoiler title="Correct Greedy Algorithm">
In this problem, the correct greedy solution is to continually flip the longest possible ranges of mismatching cows.
Mad Scientist has an excellent [editorial](http://www.usaco.org/current/data/sol_breedflip_bronze_feb20.html) with a video solution and intuitive proof.
It is highly recommended you read it to gain a better understanding of the greedy algorithm.
</spoiler>
However, not all greedy problems in the bronze division necessarily require mathematical proofs of correctness. It is often sufficent to intuitively convince yourself your algorithm is correct.
<info-block title="Pro Tip">
Sometimes, if the algorithm is easy enough to implement, you don't even need to convince yourself it's correct: just let the online judge prove it for you by coding it and seeing if it passes. Competitive programmers refer to this as "Proof by AC," or "Proof by Accepted."
<!-- Don't overuse it though? -->
</info-block>
<problems-list problems={metadata.problems.general} />
<IncompleteSection>
</IncompleteSection>
<!-- Anything else major that needs to be added? Bronze Ad Hoc is not very complicated. -->

View file

@ -0,0 +1,175 @@
---
id: intro-greedy
title: Introduction to Greedy Algorithms
author: Michael Cao, Darren Yao
description: "Selecting the optimal choice at each step in your algorithm without looking at the solution space as a whole."
frequency: 2
---
import { Problem } from "../models";
export const metadata = {
problems: {
tutorial: [
new Problem("Bronze", "Mad Scientist", "1012", "Normal", false, [], ""),
],
general: [
new Problem("Bronze", "Cow Tipping", "689", "Hard", false, [], "Cells in the last row and column can be toggled uniquely. Toggle the appropriate ones and then recurse to the rectangle in the previous row/column, and solve the same way."),
new Problem("Bronze", "Race", "989", "Very Hard", false, [], "Greedily increment/decrement Bessies speed to fit the conditions until her total distance exceeds K."),
],
}
};
## Ad Hoc
Most bronze problems fall into specific categories, such as simulation and complete search. Those which don't often require more thought, although they are not necessarily more difficult to implement.
Any problem that doesn't fall under any well-defined category can be labelled as **Ad Hoc**. Since there is no fixed algorithm or idea to solving these problems, they can be hard to generalize. In this module, we'll go over some general tips that may be useful in approaching these problems.
<ul>
<li> Draw lots of small cases to gain a better understanding of the problem. If you're having trouble debugging, draw more cases. If you don't know how to start with a problem, draw more cases. Whenever you don't know how to further approach a problem, you're probably missing an important observation, so draw more cases and make observations about properties of the problem. </li>
<li> Whenever you find an observation that seems useful, write it down! Writing down ideas lets you easily come back to them later, and makes sure you don't forget about ideas that could potentially be the solution. </li>
<li> Don't get stuck on any specific idea, unless you see an entire solution. Trying to complete search an Ad Hoc problem could end up wasting a lot of your time in contest. </li>
<li> Try to approach the problem from a lot of different perspectives. Try to draw a visual depiction of the problem, mess around with formulas, or draw a graph (see Graph Theory module). One of the most helpful things you can do when solving Ad Hoc problems is to keep trying ideas until you make progress. This is something you get better at as you do more problems.</li>
</ul>
These tips are helpful in solving Ad Hoc problems. However, in the end, the best way to get better at Ad Hoc problems (or any type of problems) is to do a lot of them. Try to solve as many practice problems below as you can, and click the solution sketch tab if you can't figure the solution out.
## Greedy Algorithms
Most USACO Bronze problems that appear to be Ad Hoc can actually be solved using **greedy** algorithms. This idea will be covered in a future [module](../silver/greedy), but we'll introduce the general mindset in this section.
<resources>
<resource source="CPH" title="6.1 - Coin Problem" starred>other examples are outside scope of bronze</resource>
</resources>
From the above:
> A **greedy** algorithm constructs a solution to the problem by always making a
choice that looks the best at the moment. A greedy algorithm never takes back
its choices, but directly constructs the final solution. For this reason, greedy
algorithms are usually very efficient.
**Greedy** does not refer to a single algorithm, but rather a way of thinking that is applied to problems; there's no one way to do greedy algorithms. Hence, we use a selection of well-known examples to help you understand the greedy paradigm.
### Example: Studying Algorithms
Steph wants to improve her knowledge of algorithms over winter break. She has a total of $X$ ($1 \leq X \leq 10^4$) minutes to dedicate to learning algorithms. There are $N$ ($1 \leq N \leq 100$) algorithms, and each one of them requires $a_i$ ($1 \leq a_i \leq 100$) minutes to learn. Find the maximum number of algorithms she can learn.
#### Solution
<spoiler title="Solution">
The first observation we make is that Steph should prioritize learning algorithms from easiest to hardest; in other words, start with learning the algorithm that requires the least amount of time, and then choose further algorithms in increasing order of time required. Let's look at the following example:
$$
X = 15, \qquad N = 6, \qquad a_i = \{ 4, 3, 8, 4, 7, 3 \}
$$
After sorting the array, we have $\{ 3, 3, 4, 4, 7, 8 \}$. Within the maximum of 15 minutes, Steph can learn four algorithms in a total of $3+3+4+4 = 14$ minutes.
The implementation of this algorithm is very simple. We sort the array, and then take as many elements as possible while the sum of times of algorithms chosen so far is less than $X$. Sorting the array takes $O(N \log N)$ time, and iterating through the array takes $O(N)$ time, for a total time complexity of $O(N \log N)$.
<LanguageSection>
<CPPSection>
```cpp
// read in the input, store the algorithms in a vector, algorithms
sort(algorithms.begin(), algorithms.end());
int count = 0; // number of minutes used so far
int i = 0;
while(count + algorithms[i] <= x){
// while there is enough time, learn more algorithms
count += algorithms[i];
i++;
}
cout << i << endl; // print the ans
```
</CPPSection>
<JavaSection>
```java
// read in the input, store the algorithms in int[] algorithms
Arrays.sort(algorithms);
int count = 0; // number of minutes used so far
int i = 0;
while(count + algorithms[i] <= x){
// while there is enough time, learn more algorithms
count += algorithms[i];
i++;
}
pw.println(i); // print the ans
pw.close();
```
</JavaSection>
</LanguageSection>
</spoiler>
### When Greedy Fails
We'll provide a few common examples of when greedy fails, so that you can avoid falling into obvious traps and wasting time getting wrong answers in contest.
#### Coin Change
This problem gives several coin denominations, and asks for the minimum number of coins needed to make a certain value. Greedy algorithms can be used to solve this problem only in very specific cases (it can be proven that it works for the American as well as the Euro coin systems). However, it doesn't work in the general case. For example, let the coin denominations be $\{1, 3, 4\}$, and say the value we want is 6. The optimal solution is $\{3, 3\}$, which requires only two coins, but the greedy method of taking the highest possible valued coin that fits in the remaining denomination gives the solution $\{4, 1, 1\}$, which is incorrect.
#### Knapsack
The knapsack problem gives a number of items, each having a **weight** and a **value**, and we want to choose a subset of these items. We are limited to a certain weight, and we want to maximize the value of the items that we take.
Let's take the following example, where we have a maximum capacity of 4:
<center>
| Item | Weight | Value | Value Per Weight |
|------|--------|-------|------------------|
| A | 3 | 18 | 6 |
| B | 2 | 10 | 5 |
| C | 2 | 10 | 5 |
</center>
If we use greedy based on highest value first, we choose item A and then we are done, as we don't have remaining weight to fit either of the other two. Using greedy based on value per weight again selects item A and then quits. However, the optimal solution is to select items B and C, as they combined have a higher value than item A alone. In fact, there is no working greedy solution. The solution to this problem uses **dynamic programming**, which is covered in gold.
### Example: Mad Scientist
Try to come up with a greedy algorithm for the USACO Bronze problem "Mad Scientist."
<problems-list problems={metadata.problems.tutorial} />
<spoiler title="Correct Greedy Algorithm">
In this problem, the correct greedy solution is to continually flip the longest possible ranges of mismatching cows.
Mad Scientist has an excellent [editorial](http://www.usaco.org/current/data/sol_breedflip_bronze_feb20.html) with a video solution and intuitive proof.
It is highly recommended you read it to gain a better understanding of the greedy algorithm.
</spoiler>
However, not all greedy problems in the bronze division necessarily require mathematical proofs of correctness. It is often sufficent to intuitively convince yourself your algorithm is correct.
<info-block title="Pro Tip">
Sometimes, if the algorithm is easy enough to implement, you don't even need to convince yourself it's correct; just code it and see if it passes. Competitive programmers refer to this as "Proof by AC," or "Proof by Accepted."
<!-- Don't overuse it though? -->
</info-block>
## Problems
<problems-list problems={metadata.problems.general} />
<IncompleteSection>
</IncompleteSection>
<!-- Anything else major that needs to be added? Bronze Ad Hoc is not very complicated. -->

View file

@ -3,9 +3,10 @@ id: greedy
title: "Greedy Algorithms"
author: Darren Yao
prerequisites:
- intro-greedy
- sorting-custom
- intro-ordered
description: "Greedily selecting the optimal choice at each step in your algorithm instead of looking at the solution space as a whole."
description: "Continuation of greedy problems that were introduced in Bronze."
frequency: 3
---
@ -44,7 +45,7 @@ export const metadata = {
};
<resources>
<resource source="IUSACO" title="9 - Greedy Algorithms" starred>Module is based off this.</resource>
<resource source="IUSACO" title="9 - Greedy Algorithms">Module is based off this.</resource>
<resource source="CPH" title="6 - Greedy Algorithms" starred></resource>
<resource source="PAPS" title="8 - Greedy Algorithms"></resource>
<resource source="CPC" title="5 - Greedy Algorithms" url="05_greedy_algorithms"></resource>
@ -52,74 +53,19 @@ export const metadata = {
<br />
**Greedy** does not refer to a single algorithm, but rather a way of thinking that is applied to problems. There's no one way to do greedy algorithms. Hence, we use a selection of well-known examples to help you understand the greedy paradigm.
Usually, when using a greedy algorithm, there is a **heuristic** or **value function** that determines which choice is considered most optimal. Here, we'll focus on problems where some sorting step is involved.
Usually, when using a greedy algorithm, there is a **heuristic** or **value function** that determines which choice is considered most optimal.
(what's a heuristic or value function?)
<IncompleteSection />
## Example: Studying Algorithms
Here, we'll focus on problems where some sorting step is involved.
### Statement
Steph wants to improve her knowledge of algorithms over winter break. She has a total of $X$ ($1 \leq X \leq 10^4$) minutes to dedicate to learning algorithms. There are $N$ ($1 \leq N \leq 100$) algorithms, and each one of them requires $a_i$ ($1 \leq a_i \leq 100$) minutes to learn. Find the maximum number of algorithms she can learn.
### Solution
The first observation we make is that Steph should prioritize learning algorithms from easiest to hardest; in other words, start with learning the algorithm that requires the least amount of time, and then choose further algorithms in increasing order of time required. Let's look at the following example:
$$X = 15, \qquad N = 6, \qquad a_i = \{ 4, 3, 8, 4, 7, 3 \}$$
After sorting the array, we have $\{ 3, 3, 4, 4, 7, 8 \}$. Within the maximum of 15 minutes, Steph can learn four algorithms in a total of $3+3+4+4 = 14$ minutes.
The implementation of this algorithm is very simple. We sort the array, and then take as many elements as possible while the sum of times of algorithms chosen so far is less than $X$. Sorting the array takes $O(N \log N)$ time, and iterating through the array takes $O(N)$ time, for a total time complexity of $O(N \log N)$.
<LanguageSection>
<CPPSection>
```cpp
// read in the input, store the algorithms in a vector, algorithms
sort(algorithms.begin(), algorithms.end());
int count = 0; // number of minutes used so far
int i = 0;
while(count + algorithms[i] <= x){
// while there is enough time, learn more algorithms
count += algorithms[i];
i++;
}
cout << i << endl; // print the ans
```
</CPPSection>
<JavaSection>
```java
// read in the input, store the algorithms in int[] algorithms
Arrays.sort(algorithms);
int count = 0; // number of minutes used so far
int i = 0;
while(count + algorithms[i] <= x){
// while there is enough time, learn more algorithms
count += algorithms[i];
i++;
}
pw.println(i); // print the ans
pw.close();
```
</JavaSection>
</LanguageSection>
## The Scheduling Problem
## Example: The Scheduling Problem
<problems-list problems={metadata.problems.movie} />
There are $N$ events, each described by their starting and ending times. Jason would like to attend as many events as possible, but he can only attend one event at a time, and if he chooses to attend an event, he must attend the entire event. Traveling between events is instantaneous.
There are $N$ events, each described by their starting and ending times. You can only attend one event at a time, and if you choose to attend an event, you must attend the entire event. Traveling between events is instantaneous. What's the maximum number of events you can attend?
### Bad Greedy: Earliest Starting Next Event
@ -228,33 +174,6 @@ pw.close();
</LanguageSection>
## When Greedy Fails
We'll provide a few common examples of when greedy fails, so that you can avoid falling into obvious traps and wasting time getting wrong answers in contest.
### Coin Change
This problem gives several coin denominations, and asks for the minimum number of coins needed to make a certain value. Greedy algorithms can be used to solve this problem only in very specific cases (it can be proven that it works for the American as well as the Euro coin systems). However, it doesn't work in the general case. For example, let the coin denominations be $\{1, 3, 4\}$, and say the value we want is 6. The optimal solution is $\{3, 3\}$, which requires only two coins, but the greedy method of taking the highest possible valued coin that fits in the remaining denomination gives the solution $\{4, 1, 1\}$, which is incorrect.
### Knapsack
The knapsack problem gives a number of items, each having a weight and a value, and we want to choose a subset of these items. We are limited to a certain weight, and we want to maximize the value of the items that we take.
Let's take the following example, where we have a maximum capacity of 4:
<center>
| Item | Weight | Value | Value Per Weight |
|------|--------|-------|------------------|
| A | 3 | 18 | 6 |
| B | 2 | 10 | 5 |
| C | 2 | 10 | 5 |
</center>
If we use greedy based on highest value first, we choose item A and then we are done, as we don't have remaining weight to fit either of the other two. Using greedy based on value per weight again selects item A and then quits. However, the optimal solution is to select items B and C, as they combined have a higher value than item A alone. In fact, there is no working greedy solution. The solution to this problem uses **dynamic programming**, which is covered in gold.
## CSES Problems
<problems-list problems={metadata.problems.cses} />

View file

@ -62,7 +62,7 @@ const MODULE_ORDERING: {[key in SectionID]: Category[]} = {
"time-comp",
"simulation",
"rect-geo",
"ad-hoc",
"intro-greedy",
]
},
{