Some fixes
This commit is contained in:
parent
eb445abef4
commit
e08db81f24
169
chapter07.tex
169
chapter07.tex
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue