More clean code
This commit is contained in:
parent
cdca81e407
commit
993b5cd8b0
|
@ -25,7 +25,7 @@ a greedy algorithm works.
|
||||||
|
|
||||||
As a first example, we consider a problem
|
As a first example, we consider a problem
|
||||||
where we are given a set of coins
|
where we are given a set of coins
|
||||||
and our task is to form a sum of money $x$
|
and our task is to form a sum of money $n$
|
||||||
using the coins.
|
using the coins.
|
||||||
The values of the coins are
|
The values of the coins are
|
||||||
$\{c_1,c_2,\ldots,c_k\}$,
|
$\{c_1,c_2,\ldots,c_k\}$,
|
||||||
|
|
291
chapter07.tex
291
chapter07.tex
|
@ -40,9 +40,9 @@ that are a good starting point.
|
||||||
|
|
||||||
We first discuss a problem that we
|
We first discuss a problem that we
|
||||||
have already seen in Chapter 6:
|
have already seen in Chapter 6:
|
||||||
Given a set of coin values $\{c_1,c_2,\ldots,c_k\}$
|
Given a set of coin values $\texttt{coins} = \{c_1,c_2,\ldots,c_k\}$
|
||||||
and a sum of money $x$, our task is to
|
and a target sum of money $n$, our task is to
|
||||||
form the sum $x$ 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 selects the largest
|
||||||
|
@ -68,107 +68,123 @@ calculates the answer to each subproblem only once.
|
||||||
|
|
||||||
The idea in dynamic programming is to
|
The idea in dynamic programming is to
|
||||||
formulate the problem recursively so
|
formulate the problem recursively so
|
||||||
that the answer to the problem can be
|
that the solution to the problem can be
|
||||||
calculated from answers to smaller
|
calculated from solutions to smaller
|
||||||
subproblems.
|
subproblems.
|
||||||
In the coin problem, a natural recursive
|
In the coin problem, a natural recursive
|
||||||
problem is as follows:
|
problem is as follows:
|
||||||
what is the smallest number of coins
|
what is the smallest number of coins
|
||||||
required to form a sum $x$?
|
required to form a sum $x$?
|
||||||
|
|
||||||
Let $f(x)$ be a function that gives the answer
|
Let $\texttt{solve}(x)$
|
||||||
to the problem, i.e., $f(x)$ is the smallest
|
denote the minimum
|
||||||
number of coins required to form a sum $x$.
|
number of coins required to form a sum $x$.
|
||||||
The values of the function depend on the
|
The values of the function depend on the
|
||||||
values of the coins.
|
values of the coins.
|
||||||
For example, if the coin values are $\{1,3,4\}$,
|
For example, if $\texttt{coins} = \{1,3,4\}$,
|
||||||
the first values of the function are as follows:
|
the first values of the function are as follows:
|
||||||
|
|
||||||
\[
|
\[
|
||||||
\begin{array}{lcl}
|
\begin{array}{lcl}
|
||||||
f(0) & = & 0 \\
|
\texttt{solve}(0) & = & 0 \\
|
||||||
f(1) & = & 1 \\
|
\texttt{solve}(1) & = & 1 \\
|
||||||
f(2) & = & 2 \\
|
\texttt{solve}(2) & = & 2 \\
|
||||||
f(3) & = & 1 \\
|
\texttt{solve}(3) & = & 1 \\
|
||||||
f(4) & = & 1 \\
|
\texttt{solve}(4) & = & 1 \\
|
||||||
f(5) & = & 2 \\
|
\texttt{solve}(5) & = & 2 \\
|
||||||
f(6) & = & 2 \\
|
\texttt{solve}(6) & = & 2 \\
|
||||||
f(7) & = & 2 \\
|
\texttt{solve}(7) & = & 2 \\
|
||||||
f(8) & = & 2 \\
|
\texttt{solve}(8) & = & 2 \\
|
||||||
f(9) & = & 3 \\
|
\texttt{solve}(9) & = & 3 \\
|
||||||
f(10) & = & 3 \\
|
\texttt{solve}(10) & = & 3 \\
|
||||||
\end{array}
|
\end{array}
|
||||||
\]
|
\]
|
||||||
|
|
||||||
First, $f(0)=0$ because no coins are needed
|
For example, $\texttt{solve}(7)=2$,
|
||||||
for the sum $0$.
|
because we need at least 2 coins
|
||||||
Then, for example, $f(3)=1$ because the sum $3$
|
to form the sum 7.
|
||||||
can be formed using coin 3,
|
In this case, the optimal solution is to choose
|
||||||
and $f(5)=2$ because the sum 5 can
|
coins 3 and 4.
|
||||||
be formed using coins 1 and 4.
|
|
||||||
|
|
||||||
The essential property of $f$ 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.
|
||||||
For example, if the coin set is $\{1,3,4\}$,
|
More precisely,
|
||||||
there are three ways how we can choose the
|
to calculate values of $\texttt{solve}$,
|
||||||
first coin in a solution.
|
we can use the following recursive function:
|
||||||
If we choose coin 1, the remaining task
|
|
||||||
is to form the sum $x-1$.
|
|
||||||
Similarly, after choosing coins 3 and 4,
|
|
||||||
the remaining sums are $x-3$ and $x-4$.
|
|
||||||
|
|
||||||
Thus, the recursive formula is
|
\begin{equation*}
|
||||||
\[f(x) = \min(f(x-1),f(x-3),f(x-4))+1\]
|
\texttt{solve}(x) = \begin{cases}
|
||||||
where the function $\min$ gives the smallest
|
\infty & x < 0\\
|
||||||
of its parameters.
|
0 & x = 0\\
|
||||||
In the general case, for a coin set
|
\min_{c \in \texttt{coins}} \texttt{solve}(x-c)+1 & x > 0 \\
|
||||||
$\{c_1,c_2,\ldots,c_k\}$,
|
\end{cases}
|
||||||
the recursive formula is
|
\end{equation*}
|
||||||
\[f(x) = \min(f(x-c_1),f(x-c_2),\ldots,f(x-c_k))+1.\]
|
|
||||||
The base case for the function is
|
First, if $x<0$, the value is $\infty$,
|
||||||
\[f(0)=0,\]
|
because it is impossible to form a negative
|
||||||
because no coins are needed for constructing
|
sum of money using any coins.
|
||||||
the sum 0.
|
Then, if $x=0$, the value is $0$,
|
||||||
In addition, it is convenient to define
|
because no coins are needed to form an empty sum.
|
||||||
\[f(x)=\infty\hspace{8px}\textrm{if $x<0$},\]
|
Finally, if $x>0$, we go through all possible ways
|
||||||
which means that to get a negative sum of money,
|
how to choose the first coin in the solution.
|
||||||
an infinite number of coins is needed.
|
The variable $c$ goes through all values in
|
||||||
This prevents the function from constructing
|
\texttt{coins} and recursively calculates the
|
||||||
a solution where the initial sum of money is negative.
|
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++:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int f(int x) {
|
int solve(int x) {
|
||||||
if (x < 0) return INF;
|
if (x < 0) return INF;
|
||||||
if (x == 0) return 0;
|
if (x == 0) return 0;
|
||||||
int best = INF;
|
int best = INF;
|
||||||
for (auto c : coins) {
|
for (auto c : coins) {
|
||||||
best = min(best, f(x-c)+1);
|
best = min(best, solve(x-c)+1);
|
||||||
}
|
}
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The code assumes that the available coins are
|
Here the constant \texttt{INF} denotes infinity.
|
||||||
stored in an array $\texttt{coins}$,
|
This function already works, but it is slow,
|
||||||
and the constant \texttt{INF} denotes infinity.
|
because there may be an exponential number of ways
|
||||||
This function works but it is not efficient yet,
|
to construct the sum.
|
||||||
because it goes through a large number
|
However, we can calculate the values of the function
|
||||||
of ways to construct the sum.
|
more efficiently by using a technique called memoization.
|
||||||
However, the function can be made efficient by
|
|
||||||
using memoization.
|
|
||||||
|
|
||||||
\subsubsection{Using memoization}
|
\subsubsection{Using memoization}
|
||||||
|
|
||||||
\index{memoization}
|
\index{memoization}
|
||||||
|
|
||||||
Dynamic programming allows us to calculate the
|
The idea of dynamic programming is to use
|
||||||
value of a recursive function efficiently
|
\key{memoization} to efficiently calculate
|
||||||
using \key{memoization}.
|
values of a recursive function.
|
||||||
This means that an auxiliary array is used
|
This means that an auxiliary array is used
|
||||||
for recording the values of the function
|
for recording the values of the function
|
||||||
for different parameters.
|
for different parameters.
|
||||||
|
@ -183,7 +199,7 @@ int value[N];
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
where $\texttt{ready}[x]$ indicates
|
where $\texttt{ready}[x]$ indicates
|
||||||
whether the value of $f(x)$ has been calculated,
|
whether the value of $\texttt{solve}(x)$ has been calculated,
|
||||||
and if it is, $\texttt{value}[x]$
|
and if it is, $\texttt{value}[x]$
|
||||||
contains this value.
|
contains this value.
|
||||||
The constant $N$ has been chosen so
|
The constant $N$ has been chosen so
|
||||||
|
@ -193,13 +209,13 @@ After this, the function can be efficiently
|
||||||
implemented as follows:
|
implemented as follows:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int f(int x) {
|
int solve(int x) {
|
||||||
if (x < 0) return INF;
|
if (x < 0) return INF;
|
||||||
if (x == 0) return 0;
|
if (x == 0) return 0;
|
||||||
if (ready[x]) return value[x];
|
if (ready[x]) return value[x];
|
||||||
int best = INF;
|
int best = INF;
|
||||||
for (auto c : coins) {
|
for (auto c : coins) {
|
||||||
best = min(best, f(x-c)+1);
|
best = min(best, solve(x-c)+1);
|
||||||
}
|
}
|
||||||
ready[x] = true;
|
ready[x] = true;
|
||||||
value[x] = best;
|
value[x] = best;
|
||||||
|
@ -211,7 +227,7 @@ The function handles the base cases
|
||||||
$x<0$ and $x=0$ as previously.
|
$x<0$ and $x=0$ as previously.
|
||||||
Then the function checks from
|
Then the function checks from
|
||||||
$\texttt{ready}[x]$ if
|
$\texttt{ready}[x]$ if
|
||||||
$f(x)$ has already been stored
|
$\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
|
||||||
|
@ -220,34 +236,34 @@ recursively and stores it in $\texttt{value}[x]$.
|
||||||
Using memoization the function works
|
Using memoization the function works
|
||||||
efficiently, because the answer for each parameter $x$
|
efficiently, because the answer for each parameter $x$
|
||||||
is calculated recursively only once.
|
is calculated recursively only once.
|
||||||
After a value of $f(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 resulting algorithm works in $O(xk)$ time,
|
The resulting algorithm works in $O(nk)$ time,
|
||||||
where the sum is $x$ 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
|
In practice, the algorithm can be used if
|
||||||
$x$ is so small that it is possible to allocate
|
$n$ is so small that it is possible to allocate
|
||||||
an array for all possible function parameters.
|
an array for all possible function parameters.
|
||||||
|
|
||||||
Note that we can also construct the array \texttt{value}
|
Note that we can also \emph{iteratively}
|
||||||
\emph{iteratively} using
|
construct the array \texttt{value} using
|
||||||
a loop that simply calculates all the values
|
a loop that simply calculates all the values
|
||||||
of $f$ for parameters $0 \ldots x$:
|
of $\texttt{solve}$ for parameters $0 \ldots n$:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
value[0] = 0;
|
value[0] = 0;
|
||||||
for (int i = 1; i <= x; i++) {
|
for (int x = 1; x <= n; x++) {
|
||||||
value[i] = INF;
|
value[x] = INF;
|
||||||
for (auto c : coins) {
|
for (auto c : coins) {
|
||||||
if (i-c >= 0) {
|
if (x-c >= 0) {
|
||||||
value[i] = min(value[i], value[i-c]+1);
|
value[x] = min(value[x], value[x-c]+1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
Since the iterative solution is shorter and a bit
|
Since the iterative solution is shorter and
|
||||||
more efficient than recursion,
|
it has lower constant factors,
|
||||||
competitive programmers often prefer this solution.
|
competitive programmers often prefer this solution.
|
||||||
|
|
||||||
\subsubsection{Constructing an example solution}
|
\subsubsection{Constructing an example solution}
|
||||||
|
@ -265,38 +281,36 @@ in an optimal solution:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
value[0] = 0;
|
value[0] = 0;
|
||||||
for (int i = 1; i <= x; i++) {
|
for (int x = 1; x <= n; x++) {
|
||||||
value[i] = INF;
|
value[x] = INF;
|
||||||
for (auto c : coins) {
|
for (auto c : coins) {
|
||||||
if (i-c < 0) continue;
|
if (x-c < 0) continue;
|
||||||
int v = value[i-c]+1;
|
int v = value[x-c]+1;
|
||||||
if (v < value[i]) {
|
if (v < value[x]) {
|
||||||
value[i] = v;
|
value[x] = v;
|
||||||
first[i] = c;
|
first[x] = c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
After this, we can print the coins that
|
After this, we can print the coins that
|
||||||
form the sum $x$ as follows:
|
form the sum $n$ as follows:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
while (x > 0) {
|
while (n > 0) {
|
||||||
cout << first[x] << "\n";
|
cout << first[n] << "\n";
|
||||||
x -= first[x];
|
n -= first[n];
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
\subsubsection{Counting the number of solutions}
|
\subsubsection{Counting the number of solutions}
|
||||||
|
|
||||||
Let us now consider a variant of the coin problem
|
Another version of the coin problem is to
|
||||||
that is otherwise like the original problem,
|
calculate the total number of ways
|
||||||
but we are asked to count the total number of solutions instead
|
to produce a sum $x$ using the coins.
|
||||||
of finding the optimal solution.
|
For example, if $\texttt{coins}=\{1,3,4\}$ and
|
||||||
For example, if the coins are $\{1,3,4\}$ and
|
$x=5$, there are a total of 6 solutions:
|
||||||
the target sum is $5$,
|
|
||||||
there are a total of 6 solutions:
|
|
||||||
|
|
||||||
\begin{multicols}{2}
|
\begin{multicols}{2}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
|
@ -309,52 +323,49 @@ there are a total of 6 solutions:
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{multicols}
|
\end{multicols}
|
||||||
|
|
||||||
The number of solutions can be calculated
|
Again, we can solve the problem recursively.
|
||||||
using the same idea as finding the optimal solution.
|
Let $\texttt{solve}(x)$ denote the number of ways
|
||||||
The difference is that when finding the optimal solution,
|
we can form the sum $x$.
|
||||||
we maximize or minimize something in the recursion,
|
For example, in the above case,
|
||||||
but now we calculate sums of numbers of solutions.
|
$\texttt{solve}(5)=6$.
|
||||||
|
The values of the function can be calculated
|
||||||
|
as follows:
|
||||||
|
\begin{equation*}
|
||||||
|
\texttt{solve}(x) = \begin{cases}
|
||||||
|
0 & x < 0\\
|
||||||
|
1 & x = 0\\
|
||||||
|
\sum_{c \in \texttt{coins}} \texttt{solve}(x-c) & x > 0 \\
|
||||||
|
\end{cases}
|
||||||
|
\end{equation*}
|
||||||
|
|
||||||
To solve the problem, we define a function $f(x)$
|
If $x<0$, the value is 0, because there are no solutions.
|
||||||
that gives the number of ways to construct
|
If $x=0$, the value is 1, because there is only one way
|
||||||
a sum $x$ using the coins.
|
to form an empty sum.
|
||||||
For example, $f(5)=6$ when the coins are $\{1,3,4\}$.
|
Otherwise we calculate the sum of all values
|
||||||
The value of $f(x)$ can be calculated recursively
|
of the form $\texttt{solve}(x-c)$ where $c$ is in \texttt{coins}.
|
||||||
using the formula
|
|
||||||
\[ f(x) = f(x-c_1)+f(x-c_2)+\cdots+f(x-c_{k}).\]
|
|
||||||
The base cases are $f(0)=1$, because there is exactly
|
|
||||||
one way to form the sum 0 using an empty set of coins,
|
|
||||||
and $f(x)=0$, when $x<0$, because it is not possible
|
|
||||||
to form a negative sum of money.
|
|
||||||
|
|
||||||
If the coin set is $\{1,3,4\}$, the function is
|
For example, if $\texttt{coins}=\{1,3,4\}$,
|
||||||
\[ f(x) = f(x-1)+f(x-3)+f(x-4) \]
|
the recursive formula for $x>0$ is
|
||||||
and the first values of the function are:
|
\begin{equation*}
|
||||||
\[
|
\begin{aligned}
|
||||||
\begin{array}{lcl}
|
\texttt{solve}(x) & = & \texttt{solve}(x-1) & + \\
|
||||||
f(0) & = & 1 \\
|
& & \texttt{solve}(x-3) & + \\
|
||||||
f(1) & = & 1 \\
|
& & \texttt{solve}(x-4) & .
|
||||||
f(2) & = & 1 \\
|
\end{aligned}
|
||||||
f(3) & = & 2 \\
|
\end{equation*}
|
||||||
f(4) & = & 4 \\
|
|
||||||
f(5) & = & 6 \\
|
|
||||||
f(6) & = & 9 \\
|
|
||||||
f(7) & = & 15 \\
|
|
||||||
f(8) & = & 25 \\
|
|
||||||
f(9) & = & 40 \\
|
|
||||||
\end{array}
|
|
||||||
\]
|
|
||||||
|
|
||||||
The following code calculates the value of $f(x)$
|
The following code constructs an array
|
||||||
using dynamic programming by filling the array
|
$\texttt{count}$ such that
|
||||||
\texttt{count} for parameters $0 \ldots x$:
|
$\texttt{count}[x]$ equals
|
||||||
|
the value of $\texttt{solve}(x)$
|
||||||
|
for $0 \le x \le n$:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
count[0] = 1;
|
count[0] = 1;
|
||||||
for (int i = 1; i <= x; i++) {
|
for (int x = 1; x <= n; i++) {
|
||||||
for (auto c : coins) {
|
for (auto c : coins) {
|
||||||
if (i-c >= 0) {
|
if (x-c >= 0) {
|
||||||
count[i] += count[i-c];
|
count[x] += count[x-c];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,11 +379,11 @@ This can be done by changing the code so that
|
||||||
all calculations are done modulo $m$.
|
all calculations are done modulo $m$.
|
||||||
In the above code, it suffices to add the line
|
In the above code, it suffices to add the line
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
count[i] %= m;
|
count[x] %= m;
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
after the line
|
after the line
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
count[i] += count[i-c];
|
count[x] += count[x-c];
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
Now we have discussed all basic
|
Now we have discussed all basic
|
||||||
|
|
Loading…
Reference in New Issue