Some fixes

This commit is contained in:
Antti H S Laaksonen 2017-05-19 20:34:24 +03:00
parent eb445abef4
commit e08db81f24
1 changed files with 82 additions and 87 deletions

View File

@ -38,14 +38,14 @@ that are a good starting point.
\section{Coin problem} \section{Coin problem}
We first discuss a problem that we We first focus on a problem that we
have already seen in Chapter 6: have already seen in Chapter 6:
Given a set of coin values $\texttt{coins} = \{c_1,c_2,\ldots,c_k\}$ Given a set of coin values $\texttt{coins} = \{c_1,c_2,\ldots,c_k\}$
and a target sum of money $n$, our task is to and a target sum of money $n$, our task is to
form the sum $n$ using as few coins as possible. form the sum $n$ using as few coins as possible.
In Chapter 6, we solved the problem using a In Chapter 6, we solved the problem using a
greedy algorithm that always selects the largest greedy algorithm that always chooses the largest
possible coin. possible coin.
The greedy algorithm works, for example, The greedy algorithm works, for example,
when the coins are the euro coins, when the coins are the euro coins,
@ -100,19 +100,40 @@ the first values of the function are as follows:
\end{array} \end{array}
\] \]
For example, $\texttt{solve}(7)=2$, For example, $\texttt{solve}(10)=3$,
because we need at least 2 coins because at least 3 coins are needed
to form the sum 7. to form the sum 10.
In this case, the optimal solution is to choose The optimal solution is $3+3+4=10$.
coins 3 and 4.
The essential property of $\texttt{solve}$ is The essential property of $\texttt{solve}$ is
that its values can be that its values can be
recursively calculated from its smaller values. recursively calculated from its smaller values.
More precisely, The idea is to focus on the \emph{first}
to calculate values of $\texttt{solve}$, coin that we choose for the sum.
we can use the following recursive function: For example, in the above example,
the first coin can be either 1, 3 or 4.
If we first choose coin 1,
the remaining task is to form the sum 9
using the minimum number of coins,
which is a subproblem of the original problem.
Of course, the same applies to coins 3 and 4.
Thus, we can use the following recursive formula
to calculate the minimum number of coins:
\begin{equation*}
\begin{aligned}
\texttt{solve}(x) & = \min( & \texttt{solve}(x-1)+1 & , \\
& & \texttt{solve}(x-3)+1 & , \\
& & \texttt{solve}(x-4)+1 & ).
\end{aligned}
\end{equation*}
The base case of the recursion is $\texttt{solve}(0)=0$,
because no coins are needed to form an empty sum.
For example,
\[ \texttt{solve}(10) = \texttt{solve}(7)+1 = \texttt{solve}(4)+2 = \texttt{solve}(0)+3 = 3.\]
Now we are ready to a general recursive function
that calculates the minimum number of
coins needed to form a sum $x$:
\begin{equation*} \begin{equation*}
\texttt{solve}(x) = \begin{cases} \texttt{solve}(x) = \begin{cases}
\infty & x < 0\\ \infty & x < 0\\
@ -123,41 +144,17 @@ we can use the following recursive function:
First, if $x<0$, the value is $\infty$, First, if $x<0$, the value is $\infty$,
because it is impossible to form a negative because it is impossible to form a negative
sum of money using any coins. sum of money.
Then, if $x=0$, the value is $0$, Then, if $x=0$, the value is $0$,
because no coins are needed to form an empty sum. because no coins are needed to form an empty sum.
Finally, if $x>0$, we go through all possible ways Finally, if $x>0$, the variable $c$ goes through
how to choose the first coin in the solution. all possibilities how to choose the first coin
The variable $c$ goes through all values in of the sum.
\texttt{coins} and recursively calculates the
minimum number of coins needed.
For example, if $\texttt{coins} = \{1,3,4\}$,
there are three ways how the
first coin in the solution can be chosen.
If we choose coin 1, the remaining task
is to form the sum $x-1$,
and $\texttt{solve}(x-1)+1$
coins are needed.
Similarly, if we choose coin 3,
$\texttt{solve}(x-3)+1$ coins are needed,
and if we choose coin 4,
$\texttt{solve}(x-4)+1$ coins are needed.
The optimal solution is the minimum
of those three values.
Thus, in this case, the recursive formula for $x>0$ is
\begin{equation*}
\begin{aligned}
\texttt{solve}(x) & = \min( & \texttt{solve}(x-1)+1 & , \\
& & \texttt{solve}(x-3)+1 & , \\
& & \texttt{solve}(x-4)+1 & ).
\end{aligned}
\end{equation*}
Once a recursive function that solves the problem Once a recursive function that solves the problem
has been found, has been found,
we can directly implement a solution in C++: we can directly implement a solution in C++
(the constant \texttt{INF} denotes infinity):
\begin{lstlisting} \begin{lstlisting}
int solve(int x) { int solve(int x) {
@ -171,12 +168,11 @@ int solve(int x) {
} }
\end{lstlisting} \end{lstlisting}
Here the constant \texttt{INF} denotes infinity. Still, this function is not efficient,
This function already works, but it is slow,
because there may be an exponential number of ways because there may be an exponential number of ways
to construct the sum. to construct the sum.
However, we can calculate the values of the function However, next we will see how to make the
more efficiently by using a technique called memoization. function efficient using a technique called memoization.
\subsubsection{Using memoization} \subsubsection{Using memoization}
@ -185,9 +181,8 @@ more efficiently by using a technique called memoization.
The idea of dynamic programming is to use The idea of dynamic programming is to use
\key{memoization} to efficiently calculate \key{memoization} to efficiently calculate
values of a recursive function. values of a recursive function.
This means that an auxiliary array is used This means that the values of the function
for recording the values of the function are stored in an array after calculating them.
for different parameters.
For each parameter, the value of the function For each parameter, the value of the function
is calculated recursively only once, and after this, is calculated recursively only once, and after this,
the value can be directly retrieved from the array. the value can be directly retrieved from the array.
@ -205,7 +200,7 @@ contains this value.
The constant $N$ has been chosen so The constant $N$ has been chosen so
that all required values fit in the arrays. that all required values fit in the arrays.
After this, the function can be efficiently Now the function can be efficiently
implemented as follows: implemented as follows:
\begin{lstlisting} \begin{lstlisting}
@ -217,8 +212,8 @@ int solve(int x) {
for (auto c : coins) { for (auto c : coins) {
best = min(best, solve(x-c)+1); best = min(best, solve(x-c)+1);
} }
ready[x] = true;
value[x] = best; value[x] = best;
ready[x] = true;
return best; return best;
} }
\end{lstlisting} \end{lstlisting}
@ -231,20 +226,17 @@ $\texttt{solve}(x)$ has already been stored
in $\texttt{value}[x]$, in $\texttt{value}[x]$,
and if it is, the function directly returns it. and if it is, the function directly returns it.
Otherwise the function calculates the value Otherwise the function calculates the value
of $\texttt{solve}(x)$
recursively and stores it in $\texttt{value}[x]$. recursively and stores it in $\texttt{value}[x]$.
Using memoization the function works This function works efficiently,
efficiently, because the answer for each parameter $x$ because the answer for each parameter $x$
is calculated recursively only once. is calculated recursively only once.
After a value of $\texttt{solve}(x)$ has been stored in $\texttt{value}[x]$, After a value of $\texttt{solve}(x)$ has been stored in $\texttt{value}[x]$,
it can be efficiently retrieved whenever the it can be efficiently retrieved whenever the
function will be called again with the parameter $x$. function will be called again with the parameter $x$.
The time complexity of the algorithm is $O(nk)$,
The resulting algorithm works in $O(nk)$ time,
where the target sum is $n$ and the number of coins is $k$. where the target sum is $n$ and the number of coins is $k$.
In practice, the algorithm can be used if
$n$ is so small that it is possible to allocate
an array for all possible function parameters.
Note that we can also \emph{iteratively} Note that we can also \emph{iteratively}
construct the array \texttt{value} using construct the array \texttt{value} using
@ -262,41 +254,45 @@ for (int x = 1; x <= n; x++) {
} }
\end{lstlisting} \end{lstlisting}
Since the iterative solution is shorter and In fact, most competitive programmers prefer this
it has lower constant factors, implementation, because it is shorter and has
competitive programmers often prefer this solution. lower constant factors.
In the sequel, we also use iterative implementations
in our examples.
Still, it is often easier to think about
dynamic programming solutions
in terms of recursive functions.
\subsubsection{Constructing an example solution}
\subsubsection{Constructing a solution}
Sometimes we are asked both to find the value Sometimes we are asked both to find the value
of an optimal solution and to give of an optimal solution and to give
an example how such a solution can be constructed. an example how such a solution can be constructed.
In the coin problem, for example, In the coin problem, for example,
we might be asked both the minimum number of coins we can declare another array
and an example how to choose the coins.
We can do this by using an array \texttt{first}
that indicates for that indicates for
each sum of money the first coin each sum of money the first coin
in an optimal solution: in an optimal solution:
\begin{lstlisting}
int first[N];
\end{lstlisting}
Then, we can modify the algorithm as follows:
\begin{lstlisting} \begin{lstlisting}
value[0] = 0; value[0] = 0;
for (int x = 1; x <= n; x++) { for (int x = 1; x <= n; x++) {
value[x] = INF; value[x] = INF;
for (auto c : coins) { for (auto c : coins) {
if (x-c < 0) continue; if (x-c >= 0 && value[x-c]+1 < value[x]) {
int v = value[x-c]+1; value[x] = value[x-c]+1;
if (v < value[x]) {
value[x] = v;
first[x] = c; first[x] = c;
} }
} }
} }
\end{lstlisting} \end{lstlisting}
After this, the following code can be used to
After this, we can print the coins that print the coins that appear in an optimal solution for
form the sum $n$ as follows: the sum $n$:
\begin{lstlisting} \begin{lstlisting}
while (n > 0) { while (n > 0) {
cout << first[n] << "\n"; cout << first[n] << "\n";
@ -306,11 +302,12 @@ while (n > 0) {
\subsubsection{Counting the number of solutions} \subsubsection{Counting the number of solutions}
Another version of the coin problem is to Let us now consider another version
of the coin problem where our task is to
calculate the total number of ways calculate the total number of ways
to produce a sum $x$ using the coins. to produce a sum $x$ using the coins.
For example, if $\texttt{coins}=\{1,3,4\}$ and For example, if $\texttt{coins}=\{1,3,4\}$ and
$x=5$, there are a total of 6 solutions: $x=5$, there are a total of 6 ways:
\begin{multicols}{2} \begin{multicols}{2}
\begin{itemize} \begin{itemize}
@ -326,10 +323,17 @@ $x=5$, there are a total of 6 solutions:
Again, we can solve the problem recursively. Again, we can solve the problem recursively.
Let $\texttt{solve}(x)$ denote the number of ways Let $\texttt{solve}(x)$ denote the number of ways
we can form the sum $x$. we can form the sum $x$.
For example, in the above case, For example, if $\texttt{coins}=\{1,3,4\}$,
$\texttt{solve}(5)=6$. then $\texttt{solve}(5)=6$ and the recursive formula is
The values of the function can be calculated \begin{equation*}
as follows: \begin{aligned}
\texttt{solve}(x) & = & \texttt{solve}(x-1) & + \\
& & \texttt{solve}(x-3) & + \\
& & \texttt{solve}(x-4) & .
\end{aligned}
\end{equation*}
In this case, the general recursive function is as follows:
\begin{equation*} \begin{equation*}
\texttt{solve}(x) = \begin{cases} \texttt{solve}(x) = \begin{cases}
0 & x < 0\\ 0 & x < 0\\
@ -344,15 +348,6 @@ to form an empty sum.
Otherwise we calculate the sum of all values Otherwise we calculate the sum of all values
of the form $\texttt{solve}(x-c)$ where $c$ is in \texttt{coins}. of the form $\texttt{solve}(x-c)$ where $c$ is in \texttt{coins}.
For example, if $\texttt{coins}=\{1,3,4\}$,
the recursive formula for $x>0$ is
\begin{equation*}
\begin{aligned}
\texttt{solve}(x) & = & \texttt{solve}(x-1) & + \\
& & \texttt{solve}(x-3) & + \\
& & \texttt{solve}(x-4) & .
\end{aligned}
\end{equation*}
The following code constructs an array The following code constructs an array
$\texttt{count}$ such that $\texttt{count}$ such that