2016-12-28 23:54:51 +01:00
|
|
|
\chapter{Dynamic programming}
|
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
\index{dynamic programming}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
\key{Dynamic programming}
|
|
|
|
is a technique that combines the correctness
|
|
|
|
of complete search and the efficiency
|
|
|
|
of greedy algorithms.
|
2017-01-31 23:08:52 +01:00
|
|
|
Dynamic programming can be applied if the
|
|
|
|
problem can be divided into overlapping subproblems
|
|
|
|
that can be solved independently.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
There are two uses for dynamic programming:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{itemize}
|
|
|
|
\item
|
2017-02-13 22:16:30 +01:00
|
|
|
\key{Finding an optimal solution}:
|
2017-01-02 18:16:30 +01:00
|
|
|
We want to find a solution that is
|
|
|
|
as large as possible or as small as possible.
|
2016-12-28 23:54:51 +01:00
|
|
|
\item
|
2017-01-31 23:08:52 +01:00
|
|
|
\key{Counting the number of solutions}:
|
2017-01-02 18:16:30 +01:00
|
|
|
We want to calculate the total number of
|
|
|
|
possible solutions.
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{itemize}
|
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
We will first see how dynamic programming can
|
2017-02-13 22:16:30 +01:00
|
|
|
be used to find an optimal solution,
|
2017-01-02 18:16:30 +01:00
|
|
|
and then we will use the same idea for
|
|
|
|
counting the solutions.
|
|
|
|
|
|
|
|
Understanding dynamic programming is a milestone
|
|
|
|
in every competitive programmer's career.
|
2017-05-18 21:00:29 +02:00
|
|
|
While the basic idea is simple,
|
|
|
|
the challenge is how to apply
|
|
|
|
dynamic programming to different problems.
|
2017-01-02 18:16:30 +01:00
|
|
|
This chapter introduces a set of classic problems
|
|
|
|
that are a good starting point.
|
|
|
|
|
|
|
|
\section{Coin problem}
|
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
We first focus on a problem that we
|
2017-01-31 23:08:52 +01:00
|
|
|
have already seen in Chapter 6:
|
2017-05-18 22:46:07 +02:00
|
|
|
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
|
|
|
|
form the sum $n$ using as few coins as possible.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
In Chapter 6, we solved the problem using a
|
2017-05-19 19:34:24 +02:00
|
|
|
greedy algorithm that always chooses the largest
|
2017-01-31 23:08:52 +01:00
|
|
|
possible coin.
|
2017-01-02 18:16:30 +01:00
|
|
|
The greedy algorithm works, for example,
|
2017-02-13 22:16:30 +01:00
|
|
|
when the coins are the euro coins,
|
2017-01-02 18:16:30 +01:00
|
|
|
but in the general case the greedy algorithm
|
2017-01-31 23:08:52 +01:00
|
|
|
does not necessarily produce an optimal solution.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-04-22 11:34:44 +02:00
|
|
|
Now is time to solve the problem efficiently
|
2017-01-31 23:08:52 +01:00
|
|
|
using dynamic programming, so that the algorithm
|
2017-01-02 18:16:30 +01:00
|
|
|
works for any coin set.
|
|
|
|
The dynamic programming
|
|
|
|
algorithm is based on a recursive function
|
|
|
|
that goes through all possibilities how to
|
2017-01-31 23:08:52 +01:00
|
|
|
form the sum, like a brute force algorithm.
|
2017-01-02 18:16:30 +01:00
|
|
|
However, the dynamic programming
|
|
|
|
algorithm is efficient because
|
2017-02-13 22:16:30 +01:00
|
|
|
it uses \emph{memoization} and
|
|
|
|
calculates the answer to each subproblem only once.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
|
|
|
\subsubsection{Recursive formulation}
|
|
|
|
|
|
|
|
The idea in dynamic programming is to
|
|
|
|
formulate the problem recursively so
|
2017-05-18 22:46:07 +02:00
|
|
|
that the solution to the problem can be
|
|
|
|
calculated from solutions to smaller
|
2017-01-02 18:16:30 +01:00
|
|
|
subproblems.
|
2017-01-31 23:08:52 +01:00
|
|
|
In the coin problem, a natural recursive
|
|
|
|
problem is as follows:
|
2017-01-02 18:16:30 +01:00
|
|
|
what is the smallest number of coins
|
2017-05-18 21:00:29 +02:00
|
|
|
required to form a sum $x$?
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-18 22:46:07 +02:00
|
|
|
Let $\texttt{solve}(x)$
|
|
|
|
denote the minimum
|
2017-05-18 21:00:29 +02:00
|
|
|
number of coins required to form a sum $x$.
|
2017-01-02 18:16:30 +01:00
|
|
|
The values of the function depend on the
|
|
|
|
values of the coins.
|
2017-05-18 22:46:07 +02:00
|
|
|
For example, if $\texttt{coins} = \{1,3,4\}$,
|
2017-01-02 18:16:30 +01:00
|
|
|
the first values of the function are as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\[
|
|
|
|
\begin{array}{lcl}
|
2017-05-18 22:46:07 +02:00
|
|
|
\texttt{solve}(0) & = & 0 \\
|
|
|
|
\texttt{solve}(1) & = & 1 \\
|
|
|
|
\texttt{solve}(2) & = & 2 \\
|
|
|
|
\texttt{solve}(3) & = & 1 \\
|
|
|
|
\texttt{solve}(4) & = & 1 \\
|
|
|
|
\texttt{solve}(5) & = & 2 \\
|
|
|
|
\texttt{solve}(6) & = & 2 \\
|
|
|
|
\texttt{solve}(7) & = & 2 \\
|
|
|
|
\texttt{solve}(8) & = & 2 \\
|
|
|
|
\texttt{solve}(9) & = & 3 \\
|
|
|
|
\texttt{solve}(10) & = & 3 \\
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{array}
|
|
|
|
\]
|
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
For example, $\texttt{solve}(10)=3$,
|
|
|
|
because at least 3 coins are needed
|
|
|
|
to form the sum 10.
|
|
|
|
The optimal solution is $3+3+4=10$.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-18 22:46:07 +02:00
|
|
|
The essential property of $\texttt{solve}$ is
|
2017-05-18 21:00:29 +02:00
|
|
|
that its values can be
|
|
|
|
recursively calculated from its smaller values.
|
2017-05-19 19:34:24 +02:00
|
|
|
The idea is to focus on the \emph{first}
|
|
|
|
coin that we choose for the sum.
|
|
|
|
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.\]
|
2017-05-18 22:46:07 +02:00
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
Now we are ready to a general recursive function
|
|
|
|
that calculates the minimum number of
|
|
|
|
coins needed to form a sum $x$:
|
2017-05-18 22:46:07 +02:00
|
|
|
\begin{equation*}
|
|
|
|
\texttt{solve}(x) = \begin{cases}
|
|
|
|
\infty & x < 0\\
|
|
|
|
0 & x = 0\\
|
|
|
|
\min_{c \in \texttt{coins}} \texttt{solve}(x-c)+1 & x > 0 \\
|
|
|
|
\end{cases}
|
|
|
|
\end{equation*}
|
|
|
|
|
|
|
|
First, if $x<0$, the value is $\infty$,
|
|
|
|
because it is impossible to form a negative
|
2017-05-19 19:34:24 +02:00
|
|
|
sum of money.
|
2017-05-18 22:46:07 +02:00
|
|
|
Then, if $x=0$, the value is $0$,
|
|
|
|
because no coins are needed to form an empty sum.
|
2017-05-19 19:34:24 +02:00
|
|
|
Finally, if $x>0$, the variable $c$ goes through
|
|
|
|
all possibilities how to choose the first coin
|
|
|
|
of the sum.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
Once a recursive function that solves the problem
|
|
|
|
has been found,
|
2017-05-19 19:34:24 +02:00
|
|
|
we can directly implement a solution in C++
|
|
|
|
(the constant \texttt{INF} denotes infinity):
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{lstlisting}
|
2017-05-18 22:46:07 +02:00
|
|
|
int solve(int x) {
|
2017-04-17 13:38:33 +02:00
|
|
|
if (x < 0) return INF;
|
2017-02-27 20:29:32 +01:00
|
|
|
if (x == 0) return 0;
|
2017-05-18 21:00:29 +02:00
|
|
|
int best = INF;
|
|
|
|
for (auto c : coins) {
|
2017-05-18 22:46:07 +02:00
|
|
|
best = min(best, solve(x-c)+1);
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
2017-05-18 21:00:29 +02:00
|
|
|
return best;
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
Still, this function is not efficient,
|
2017-05-18 22:46:07 +02:00
|
|
|
because there may be an exponential number of ways
|
|
|
|
to construct the sum.
|
2017-05-19 19:34:24 +02:00
|
|
|
However, next we will see how to make the
|
|
|
|
function efficient using a technique called memoization.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-18 21:00:29 +02:00
|
|
|
\subsubsection{Using memoization}
|
2017-01-02 18:16:30 +01:00
|
|
|
|
|
|
|
\index{memoization}
|
|
|
|
|
2017-05-18 22:46:07 +02:00
|
|
|
The idea of dynamic programming is to use
|
|
|
|
\key{memoization} to efficiently calculate
|
|
|
|
values of a recursive function.
|
2017-05-19 19:34:24 +02:00
|
|
|
This means that the values of the function
|
|
|
|
are stored in an array after calculating them.
|
2017-01-02 18:16:30 +01:00
|
|
|
For each parameter, the value of the function
|
2017-01-31 23:08:52 +01:00
|
|
|
is calculated recursively only once, and after this,
|
|
|
|
the value can be directly retrieved from the array.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-18 21:00:29 +02:00
|
|
|
In this problem, we use arrays
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 21:00:29 +02:00
|
|
|
bool ready[N];
|
|
|
|
int value[N];
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-05-18 21:00:29 +02:00
|
|
|
where $\texttt{ready}[x]$ indicates
|
2017-05-18 22:46:07 +02:00
|
|
|
whether the value of $\texttt{solve}(x)$ has been calculated,
|
2017-05-18 21:00:29 +02:00
|
|
|
and if it is, $\texttt{value}[x]$
|
|
|
|
contains this value.
|
|
|
|
The constant $N$ has been chosen so
|
|
|
|
that all required values fit in the arrays.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
Now the function can be efficiently
|
2017-01-02 18:16:30 +01:00
|
|
|
implemented as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{lstlisting}
|
2017-05-18 22:46:07 +02:00
|
|
|
int solve(int x) {
|
2017-04-17 13:38:33 +02:00
|
|
|
if (x < 0) return INF;
|
2017-02-27 20:29:32 +01:00
|
|
|
if (x == 0) return 0;
|
2017-05-18 21:00:29 +02:00
|
|
|
if (ready[x]) return value[x];
|
|
|
|
int best = INF;
|
|
|
|
for (auto c : coins) {
|
2017-05-18 22:46:07 +02:00
|
|
|
best = min(best, solve(x-c)+1);
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
2017-05-18 21:00:29 +02:00
|
|
|
value[x] = best;
|
2017-05-19 19:34:24 +02:00
|
|
|
ready[x] = true;
|
2017-05-18 21:00:29 +02:00
|
|
|
return best;
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
The function handles the base cases
|
2017-02-27 20:29:32 +01:00
|
|
|
$x<0$ and $x=0$ as previously.
|
2017-05-18 21:00:29 +02:00
|
|
|
Then the function checks from
|
|
|
|
$\texttt{ready}[x]$ if
|
2017-05-18 22:46:07 +02:00
|
|
|
$\texttt{solve}(x)$ has already been stored
|
2017-05-18 21:00:29 +02:00
|
|
|
in $\texttt{value}[x]$,
|
|
|
|
and if it is, the function directly returns it.
|
2017-01-02 18:16:30 +01:00
|
|
|
Otherwise the function calculates the value
|
2017-05-19 19:34:24 +02:00
|
|
|
of $\texttt{solve}(x)$
|
2017-05-18 21:00:29 +02:00
|
|
|
recursively and stores it in $\texttt{value}[x]$.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
This function works efficiently,
|
|
|
|
because the answer for each parameter $x$
|
2017-01-31 23:08:52 +01:00
|
|
|
is calculated recursively only once.
|
2017-05-18 22:46:07 +02:00
|
|
|
After a value of $\texttt{solve}(x)$ has been stored in $\texttt{value}[x]$,
|
2017-01-31 23:08:52 +01:00
|
|
|
it can be efficiently retrieved whenever the
|
2017-02-13 22:16:30 +01:00
|
|
|
function will be called again with the parameter $x$.
|
2017-05-19 19:34:24 +02:00
|
|
|
The time complexity of the algorithm is $O(nk)$,
|
2017-05-18 22:46:07 +02:00
|
|
|
where the target sum is $n$ and the number of coins is $k$.
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-18 22:46:07 +02:00
|
|
|
Note that we can also \emph{iteratively}
|
|
|
|
construct the array \texttt{value} using
|
2017-05-18 21:00:29 +02:00
|
|
|
a loop that simply calculates all the values
|
2017-05-18 22:46:07 +02:00
|
|
|
of $\texttt{solve}$ for parameters $0 \ldots n$:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 21:00:29 +02:00
|
|
|
value[0] = 0;
|
2017-05-18 22:46:07 +02:00
|
|
|
for (int x = 1; x <= n; x++) {
|
|
|
|
value[x] = INF;
|
2017-05-18 21:00:29 +02:00
|
|
|
for (auto c : coins) {
|
2017-05-18 22:46:07 +02:00
|
|
|
if (x-c >= 0) {
|
|
|
|
value[x] = min(value[x], value[x-c]+1);
|
2017-05-18 21:00:29 +02:00
|
|
|
}
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
In fact, most competitive programmers prefer this
|
|
|
|
implementation, because it is shorter and has
|
|
|
|
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.
|
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
\subsubsection{Constructing a solution}
|
2017-01-02 18:16:30 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
Sometimes we are asked both to find the value
|
2017-05-18 21:00:29 +02:00
|
|
|
of an optimal solution and to give
|
2017-01-02 18:16:30 +01:00
|
|
|
an example how such a solution can be constructed.
|
2017-05-18 21:00:29 +02:00
|
|
|
In the coin problem, for example,
|
2017-05-19 19:34:24 +02:00
|
|
|
we can declare another array
|
2017-05-18 21:00:29 +02:00
|
|
|
that indicates for
|
|
|
|
each sum of money the first coin
|
|
|
|
in an optimal solution:
|
2017-05-19 19:34:24 +02:00
|
|
|
\begin{lstlisting}
|
|
|
|
int first[N];
|
|
|
|
\end{lstlisting}
|
|
|
|
Then, we can modify the algorithm as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 21:00:29 +02:00
|
|
|
value[0] = 0;
|
2017-05-18 22:46:07 +02:00
|
|
|
for (int x = 1; x <= n; x++) {
|
|
|
|
value[x] = INF;
|
2017-05-18 21:00:29 +02:00
|
|
|
for (auto c : coins) {
|
2017-05-19 19:34:24 +02:00
|
|
|
if (x-c >= 0 && value[x-c]+1 < value[x]) {
|
|
|
|
value[x] = value[x-c]+1;
|
2017-05-18 22:46:07 +02:00
|
|
|
first[x] = c;
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
\end{lstlisting}
|
2017-05-19 19:34:24 +02:00
|
|
|
After this, the following code can be used to
|
|
|
|
print the coins that appear in an optimal solution for
|
|
|
|
the sum $n$:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 22:46:07 +02:00
|
|
|
while (n > 0) {
|
|
|
|
cout << first[n] << "\n";
|
|
|
|
n -= first[n];
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-01-02 18:16:30 +01:00
|
|
|
\subsubsection{Counting the number of solutions}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-05-19 19:34:24 +02:00
|
|
|
Let us now consider another version
|
|
|
|
of the coin problem where our task is to
|
2017-05-18 22:46:07 +02:00
|
|
|
calculate the total number of ways
|
|
|
|
to produce a sum $x$ using the coins.
|
|
|
|
For example, if $\texttt{coins}=\{1,3,4\}$ and
|
2017-05-19 19:34:24 +02:00
|
|
|
$x=5$, there are a total of 6 ways:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{multicols}{2}
|
|
|
|
\begin{itemize}
|
|
|
|
\item $1+1+1+1+1$
|
|
|
|
\item $1+1+3$
|
|
|
|
\item $1+3+1$
|
|
|
|
\item $3+1+1$
|
|
|
|
\item $1+4$
|
|
|
|
\item $4+1$
|
|
|
|
\end{itemize}
|
|
|
|
\end{multicols}
|
|
|
|
|
2017-05-18 22:46:07 +02:00
|
|
|
Again, we can solve the problem recursively.
|
|
|
|
Let $\texttt{solve}(x)$ denote the number of ways
|
|
|
|
we can form the sum $x$.
|
2017-05-19 19:34:24 +02:00
|
|
|
For example, if $\texttt{coins}=\{1,3,4\}$,
|
|
|
|
then $\texttt{solve}(5)=6$ and the recursive formula is
|
|
|
|
\begin{equation*}
|
|
|
|
\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:
|
2017-05-18 22:46:07 +02:00
|
|
|
\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*}
|
|
|
|
|
|
|
|
If $x<0$, the value is 0, because there are no solutions.
|
|
|
|
If $x=0$, the value is 1, because there is only one way
|
|
|
|
to form an empty sum.
|
|
|
|
Otherwise we calculate the sum of all values
|
|
|
|
of the form $\texttt{solve}(x-c)$ where $c$ is in \texttt{coins}.
|
|
|
|
|
|
|
|
|
|
|
|
The following code constructs an array
|
|
|
|
$\texttt{count}$ such that
|
|
|
|
$\texttt{count}[x]$ equals
|
|
|
|
the value of $\texttt{solve}(x)$
|
|
|
|
for $0 \le x \le n$:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{lstlisting}
|
2017-05-18 21:00:29 +02:00
|
|
|
count[0] = 1;
|
2017-05-18 22:46:07 +02:00
|
|
|
for (int x = 1; x <= n; i++) {
|
2017-05-18 21:00:29 +02:00
|
|
|
for (auto c : coins) {
|
2017-05-18 22:46:07 +02:00
|
|
|
if (x-c >= 0) {
|
|
|
|
count[x] += count[x-c];
|
2017-05-18 21:00:29 +02:00
|
|
|
}
|
2016-12-28 23:54:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
Often the number of solutions is so large
|
2017-01-02 18:16:30 +01:00
|
|
|
that it is not required to calculate the exact number
|
|
|
|
but it is enough to give the answer modulo $m$
|
|
|
|
where, for example, $m=10^9+7$.
|
|
|
|
This can be done by changing the code so that
|
2017-02-13 22:16:30 +01:00
|
|
|
all calculations are done modulo $m$.
|
|
|
|
In the above code, it suffices to add the line
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 22:46:07 +02:00
|
|
|
count[x] %= m;
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{lstlisting}
|
2017-01-02 18:16:30 +01:00
|
|
|
after the line
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{lstlisting}
|
2017-05-18 22:46:07 +02:00
|
|
|
count[x] += count[x-c];
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{lstlisting}
|
|
|
|
|
2017-02-13 22:16:30 +01:00
|
|
|
Now we have discussed all basic
|
2017-05-18 21:00:29 +02:00
|
|
|
ideas of dynamic programming.
|
2017-01-02 18:16:30 +01:00
|
|
|
Since dynamic programming can be used
|
|
|
|
in many different situations,
|
|
|
|
we will now go through a set of problems
|
2017-05-18 21:00:29 +02:00
|
|
|
that show further examples about the
|
2017-01-31 23:08:52 +01:00
|
|
|
possibilities of dynamic programming.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 18:55:47 +01:00
|
|
|
\section{Longest increasing subsequence}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 18:55:47 +01:00
|
|
|
\index{longest increasing subsequence}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-04-18 19:22:56 +02:00
|
|
|
Let us consider the following problem:
|
2017-01-02 18:55:47 +01:00
|
|
|
Given an array that contains $n$
|
2017-04-17 16:59:27 +02:00
|
|
|
numbers,
|
2017-01-31 23:08:52 +01:00
|
|
|
our task is to find the
|
2017-01-02 18:55:47 +01:00
|
|
|
\key{longest increasing subsequence}
|
2017-02-13 22:16:30 +01:00
|
|
|
of the array.
|
2017-01-02 18:55:47 +01:00
|
|
|
This is a sequence of array elements
|
2017-01-31 23:08:52 +01:00
|
|
|
that goes from left to right,
|
2017-04-18 19:22:56 +02:00
|
|
|
and each element of the sequence is larger
|
2017-01-02 18:55:47 +01:00
|
|
|
than the previous element.
|
|
|
|
For example, in the array
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=0.7]
|
|
|
|
\draw (0,0) grid (8,1);
|
|
|
|
\node at (0.5,0.5) {$6$};
|
|
|
|
\node at (1.5,0.5) {$2$};
|
|
|
|
\node at (2.5,0.5) {$5$};
|
|
|
|
\node at (3.5,0.5) {$1$};
|
|
|
|
\node at (4.5,0.5) {$7$};
|
|
|
|
\node at (5.5,0.5) {$4$};
|
|
|
|
\node at (6.5,0.5) {$8$};
|
|
|
|
\node at (7.5,0.5) {$3$};
|
|
|
|
|
|
|
|
\footnotesize
|
2017-04-17 16:59:27 +02:00
|
|
|
\node at (0.5,1.4) {$0$};
|
|
|
|
\node at (1.5,1.4) {$1$};
|
|
|
|
\node at (2.5,1.4) {$2$};
|
|
|
|
\node at (3.5,1.4) {$3$};
|
|
|
|
\node at (4.5,1.4) {$4$};
|
|
|
|
\node at (5.5,1.4) {$5$};
|
|
|
|
\node at (6.5,1.4) {$6$};
|
|
|
|
\node at (7.5,1.4) {$7$};
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
2017-01-02 18:55:47 +01:00
|
|
|
the longest increasing subsequence
|
|
|
|
contains 4 elements:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=0.7]
|
|
|
|
\fill[color=lightgray] (1,0) rectangle (2,1);
|
|
|
|
\fill[color=lightgray] (2,0) rectangle (3,1);
|
|
|
|
\fill[color=lightgray] (4,0) rectangle (5,1);
|
|
|
|
\fill[color=lightgray] (6,0) rectangle (7,1);
|
|
|
|
\draw (0,0) grid (8,1);
|
|
|
|
\node at (0.5,0.5) {$6$};
|
|
|
|
\node at (1.5,0.5) {$2$};
|
|
|
|
\node at (2.5,0.5) {$5$};
|
|
|
|
\node at (3.5,0.5) {$1$};
|
|
|
|
\node at (4.5,0.5) {$7$};
|
|
|
|
\node at (5.5,0.5) {$4$};
|
|
|
|
\node at (6.5,0.5) {$8$};
|
|
|
|
\node at (7.5,0.5) {$3$};
|
|
|
|
|
|
|
|
\draw[thick,->] (1.5,-0.25) .. controls (1.75,-1.00) and (2.25,-1.00) .. (2.4,-0.25);
|
|
|
|
\draw[thick,->] (2.6,-0.25) .. controls (3.0,-1.00) and (4.0,-1.00) .. (4.4,-0.25);
|
|
|
|
\draw[thick,->] (4.6,-0.25) .. controls (5.0,-1.00) and (6.0,-1.00) .. (6.5,-0.25);
|
|
|
|
|
|
|
|
\footnotesize
|
2017-04-17 16:59:27 +02:00
|
|
|
\node at (0.5,1.4) {$0$};
|
|
|
|
\node at (1.5,1.4) {$1$};
|
|
|
|
\node at (2.5,1.4) {$2$};
|
|
|
|
\node at (3.5,1.4) {$3$};
|
|
|
|
\node at (4.5,1.4) {$4$};
|
|
|
|
\node at (5.5,1.4) {$5$};
|
|
|
|
\node at (6.5,1.4) {$6$};
|
|
|
|
\node at (7.5,1.4) {$7$};
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
|
|
|
|
2017-01-02 18:55:47 +01:00
|
|
|
Let $f(k)$ be the length of the
|
|
|
|
longest increasing subsequence
|
2017-01-31 23:08:52 +01:00
|
|
|
that ends at position $k$.
|
|
|
|
Using this function, the answer to the problem
|
2017-01-31 23:21:48 +01:00
|
|
|
is the largest of the values
|
2017-04-17 16:59:27 +02:00
|
|
|
$f(0),f(1),\ldots,f(n-1)$.
|
2017-01-02 18:55:47 +01:00
|
|
|
For example, in the above array
|
2017-01-31 23:08:52 +01:00
|
|
|
the values of the function are as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
\[
|
|
|
|
\begin{array}{lcl}
|
2017-04-17 16:59:27 +02:00
|
|
|
f(0) & = & 1 \\
|
2016-12-28 23:54:51 +01:00
|
|
|
f(1) & = & 1 \\
|
2017-04-17 16:59:27 +02:00
|
|
|
f(2) & = & 2 \\
|
|
|
|
f(3) & = & 1 \\
|
|
|
|
f(4) & = & 3 \\
|
|
|
|
f(5) & = & 2 \\
|
|
|
|
f(6) & = & 4 \\
|
|
|
|
f(7) & = & 2 \\
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{array}
|
|
|
|
\]
|
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
When calculating the value of $f(k)$,
|
2017-01-02 18:55:47 +01:00
|
|
|
there are two possibilities how the subsequence
|
2017-01-31 23:08:52 +01:00
|
|
|
that ends at position $k$ is constructed:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{enumerate}
|
2017-01-02 18:55:47 +01:00
|
|
|
\item The subsequence
|
2017-04-17 16:59:27 +02:00
|
|
|
only contains the element at position $k$. In this case $f(k)=1$.
|
2017-04-18 19:22:56 +02:00
|
|
|
\item The subsequence contains a subsequence
|
|
|
|
that ends at position $i$ where $i<k$,
|
|
|
|
followed by the element at position $k$.
|
|
|
|
The element at position $i$ must be smaller
|
|
|
|
than the element at position $k$.
|
|
|
|
In this case $f(k)=f(i)+1$.
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{enumerate}
|
|
|
|
|
2017-04-18 19:22:56 +02:00
|
|
|
For example, in the above example $f(6)=4$,
|
2017-01-31 23:21:48 +01:00
|
|
|
because the subsequence $[2,5,7]$ of length 3
|
2017-04-18 19:22:56 +02:00
|
|
|
ends at position 4, and by adding the element
|
|
|
|
at position 6 to this subsequence,
|
2017-01-31 23:21:48 +01:00
|
|
|
we get the optimal subsequence $[2,5,7,8]$ of length 4.
|
2017-01-02 18:55:47 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
An easy way to calculate the
|
|
|
|
value of $f(k)$ is to
|
2017-01-31 23:21:48 +01:00
|
|
|
go through all previous values
|
2017-04-18 19:22:56 +02:00
|
|
|
$f(0),f(1),\ldots,f(k-1)$ and select the best solution.
|
2017-01-02 18:55:47 +01:00
|
|
|
The time complexity of such an algorithm is $O(n^2)$.
|
|
|
|
Surprisingly, it is also possible to solve the
|
2017-02-20 22:23:10 +01:00
|
|
|
problem in $O(n \log n)$ time. Can you see how?
|
2017-01-02 18:55:47 +01:00
|
|
|
|
2017-02-20 22:23:10 +01:00
|
|
|
\section{Paths in a grid}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 19:14:49 +01:00
|
|
|
Our next problem is to find a path
|
|
|
|
in an $n \times n$ grid
|
|
|
|
from the upper-left corner to
|
2017-01-31 23:08:52 +01:00
|
|
|
the lower-right corner such that
|
2017-02-13 22:16:30 +01:00
|
|
|
we only move down and right.
|
2017-01-02 19:14:49 +01:00
|
|
|
Each square contains a number,
|
|
|
|
and the path should be constructed so
|
2017-04-18 19:22:56 +02:00
|
|
|
that the sum of the numbers along
|
2017-01-02 19:14:49 +01:00
|
|
|
the path is as large as possible.
|
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
The following picture shows an optimal
|
|
|
|
path in a grid:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=.65]
|
|
|
|
\begin{scope}
|
|
|
|
\fill [color=lightgray] (0, 9) rectangle (1, 8);
|
|
|
|
\fill [color=lightgray] (0, 8) rectangle (1, 7);
|
|
|
|
\fill [color=lightgray] (1, 8) rectangle (2, 7);
|
|
|
|
\fill [color=lightgray] (1, 7) rectangle (2, 6);
|
|
|
|
\fill [color=lightgray] (2, 7) rectangle (3, 6);
|
|
|
|
\fill [color=lightgray] (3, 7) rectangle (4, 6);
|
|
|
|
\fill [color=lightgray] (4, 7) rectangle (5, 6);
|
|
|
|
\fill [color=lightgray] (4, 6) rectangle (5, 5);
|
|
|
|
\fill [color=lightgray] (4, 5) rectangle (5, 4);
|
|
|
|
\draw (0, 4) grid (5, 9);
|
|
|
|
\node at (0.5,8.5) {3};
|
|
|
|
\node at (1.5,8.5) {7};
|
|
|
|
\node at (2.5,8.5) {9};
|
|
|
|
\node at (3.5,8.5) {2};
|
|
|
|
\node at (4.5,8.5) {7};
|
|
|
|
\node at (0.5,7.5) {9};
|
|
|
|
\node at (1.5,7.5) {8};
|
|
|
|
\node at (2.5,7.5) {3};
|
|
|
|
\node at (3.5,7.5) {5};
|
|
|
|
\node at (4.5,7.5) {5};
|
|
|
|
\node at (0.5,6.5) {1};
|
|
|
|
\node at (1.5,6.5) {7};
|
|
|
|
\node at (2.5,6.5) {9};
|
|
|
|
\node at (3.5,6.5) {8};
|
|
|
|
\node at (4.5,6.5) {5};
|
|
|
|
\node at (0.5,5.5) {3};
|
|
|
|
\node at (1.5,5.5) {8};
|
|
|
|
\node at (2.5,5.5) {6};
|
|
|
|
\node at (3.5,5.5) {4};
|
|
|
|
\node at (4.5,5.5) {10};
|
|
|
|
\node at (0.5,4.5) {6};
|
|
|
|
\node at (1.5,4.5) {3};
|
|
|
|
\node at (2.5,4.5) {9};
|
|
|
|
\node at (3.5,4.5) {7};
|
|
|
|
\node at (4.5,4.5) {8};
|
|
|
|
\end{scope}
|
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
2017-04-18 19:22:56 +02:00
|
|
|
The sum of the numbers on the path is 67,
|
2017-01-31 23:08:52 +01:00
|
|
|
and this is the largest possible sum on a path
|
2017-01-02 19:14:49 +01:00
|
|
|
from the
|
|
|
|
upper-left corner to the lower-right corner.
|
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
We can approach the problem by
|
|
|
|
calculating for each square $(y,x)$
|
2017-02-13 22:16:30 +01:00
|
|
|
the maximum sum on a path
|
2017-01-31 23:08:52 +01:00
|
|
|
from the upper-left corner to square $(y,x)$.
|
|
|
|
Let $f(y,x)$ denote this sum,
|
2017-02-13 22:16:30 +01:00
|
|
|
so $f(n,n)$ is the maximum sum on a path
|
2017-01-02 19:14:49 +01:00
|
|
|
from the upper-left corner to
|
|
|
|
the lower-right corner.
|
|
|
|
|
|
|
|
The recursive formula is based on the observation
|
2017-01-31 23:08:52 +01:00
|
|
|
that a path that ends at square $(y,x)$
|
2017-02-13 22:16:30 +01:00
|
|
|
can come either from square $(y,x-1)$
|
|
|
|
or square $(y-1,x)$:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=.65]
|
|
|
|
\begin{scope}
|
|
|
|
\fill [color=lightgray] (3, 7) rectangle (4, 6);
|
|
|
|
\draw (0, 4) grid (5, 9);
|
|
|
|
|
|
|
|
\node at (2.5,6.5) {$\rightarrow$};
|
|
|
|
\node at (3.5,7.5) {$\downarrow$};
|
|
|
|
|
|
|
|
\end{scope}
|
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
|
|
|
|
2017-01-02 19:14:49 +01:00
|
|
|
Let $r(y,x)$ denote the number in square $(y,x)$.
|
|
|
|
The base cases for the recursive function
|
|
|
|
are as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\[
|
|
|
|
\begin{array}{lcl}
|
|
|
|
f(1,1) & = & r(1,1) \\
|
|
|
|
f(1,x) & = & f(1,x-1)+r(1,x) \\
|
|
|
|
f(y,1) & = & f(y-1,1)+r(y,1)\\
|
|
|
|
\end{array}
|
|
|
|
\]
|
|
|
|
|
2017-01-02 19:14:49 +01:00
|
|
|
In the general case there are two
|
|
|
|
possible paths, and we should select the path
|
|
|
|
that produces the larger sum:
|
2016-12-28 23:54:51 +01:00
|
|
|
\[ f(y,x) = \max(f(y,x-1),f(y-1,x))+r(y,x)\]
|
|
|
|
|
2017-01-02 19:14:49 +01:00
|
|
|
The time complexity of the solution is $O(n^2)$,
|
|
|
|
because each value $f(y,x)$ can be calculated
|
|
|
|
in constant time using the values of the
|
|
|
|
adjacent squares.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 19:29:56 +01:00
|
|
|
\section{Knapsack}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 19:29:56 +01:00
|
|
|
\index{knapsack}
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 19:29:56 +01:00
|
|
|
\key{Knapsack} is a classic problem where we
|
|
|
|
are given $n$ objects with weights
|
|
|
|
$p_1,p_2,\ldots,p_n$ and values
|
2016-12-28 23:54:51 +01:00
|
|
|
$a_1,a_2,\ldots,a_n$.
|
2017-01-02 19:29:56 +01:00
|
|
|
Our task is to choose a subset of the objects
|
|
|
|
such that the sum of the weights is at most $x$
|
|
|
|
and the sum of the values is as large as possible.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{samepage}
|
2017-01-02 19:29:56 +01:00
|
|
|
For example, if the objects are
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tabular}{rrr}
|
2017-01-02 19:29:56 +01:00
|
|
|
object & weight & value \\
|
2016-12-28 23:54:51 +01:00
|
|
|
\hline
|
|
|
|
A & 5 & 1 \\
|
|
|
|
B & 6 & 3 \\
|
|
|
|
C & 8 & 5 \\
|
|
|
|
D & 5 & 3 \\
|
|
|
|
\end{tabular}
|
|
|
|
\end{center}
|
|
|
|
\end{samepage}
|
2017-01-31 23:08:52 +01:00
|
|
|
and the maximum allowed total weight is 12,
|
|
|
|
an optimal solution is to select objects $B$ and $D$.
|
|
|
|
Their total weight $6+5=11$ does not exceed 12,
|
|
|
|
and their total value $3+3=6$ is the largest possible.
|
2017-01-02 19:29:56 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
This task can be solved in two different ways
|
2017-01-02 19:29:56 +01:00
|
|
|
using dynamic programming.
|
|
|
|
We can either regard the problem as maximizing the
|
|
|
|
total value of the objects or
|
|
|
|
minimizing the total weight of the objects.
|
|
|
|
|
|
|
|
\subsubsection{Solution 1}
|
|
|
|
|
|
|
|
\textit{Maximization:} Let $f(k,u)$
|
|
|
|
denote the largest possible total value
|
|
|
|
when a subset of objects $1 \ldots k$ is selected
|
|
|
|
such that the total weight is $u$.
|
2017-01-31 23:08:52 +01:00
|
|
|
The solution to the problem is
|
2017-01-02 19:29:56 +01:00
|
|
|
the largest value
|
|
|
|
$f(n,u)$ where $0 \le u \le x$.
|
|
|
|
A recursive formula for calculating
|
|
|
|
the function is
|
2017-01-31 23:08:52 +01:00
|
|
|
\[f(k,u) = \max(f(k-1,u),f(k-1,u-p_k)+a_k),\]
|
2017-01-02 19:29:56 +01:00
|
|
|
because we can either include or not include
|
|
|
|
object $k$ in the solution.
|
|
|
|
The base cases are $f(0,0)=0$ and $f(0,u)=-\infty$
|
|
|
|
when $u \neq 0$. The time compexity of
|
|
|
|
the solution is $O(nx)$.
|
|
|
|
|
|
|
|
In the example case, the optimal solution is
|
|
|
|
$f(4,11)=6$ that can be constructed
|
|
|
|
using the following sequence:
|
2016-12-28 23:54:51 +01:00
|
|
|
\[f(4,11)=f(3,6)+3=f(2,6)+3=f(1,0)+3+3=f(0,0)+3+3=6.\]
|
|
|
|
|
2017-01-02 19:29:56 +01:00
|
|
|
\subsubsection{Solution 2}
|
|
|
|
|
2017-01-02 19:31:52 +01:00
|
|
|
\textit{Minimization:} Let $f(k,u)$
|
2017-01-02 19:29:56 +01:00
|
|
|
denote the smallest possible total weight
|
|
|
|
when a subset of objects
|
|
|
|
$1 \ldots k$ is selected such
|
2017-04-24 00:48:15 +02:00
|
|
|
that the total value is $u$.
|
2017-02-13 22:16:30 +01:00
|
|
|
The solution to the problem is the
|
2017-01-02 19:29:56 +01:00
|
|
|
largest value $u$
|
|
|
|
for which $0 \le u \le s$ and $f(n,u) \le x$
|
|
|
|
where $s=\sum_{i=1}^n a_i$.
|
|
|
|
A recursive formula for calculating the function is
|
2017-01-31 23:08:52 +01:00
|
|
|
\[f(k,u) = \min(f(k-1,u),f(k-1,u-a_k)+p_k)\]
|
2017-01-02 19:29:56 +01:00
|
|
|
as in solution 1.
|
|
|
|
The base cases are $f(0,0)=0$ and $f(0,u)=\infty$
|
|
|
|
when $u \neq 0$.
|
|
|
|
The time complexity of the solution is $O(ns)$.
|
|
|
|
|
|
|
|
In the example case, the optimal solution is $f(4,6)=11$
|
|
|
|
that can be constructed using the following sequence:
|
2016-12-28 23:54:51 +01:00
|
|
|
\[f(4,6)=f(3,3)+5=f(2,3)+5=f(1,0)+6+5=f(0,0)+6+5=11.\]
|
|
|
|
|
|
|
|
~\\
|
2017-01-31 23:08:52 +01:00
|
|
|
It is interesting to note how the parameters of the input
|
|
|
|
affect the efficiency of the solutions.
|
2017-01-02 19:29:56 +01:00
|
|
|
The efficiency of solution 1 depends on the weights
|
|
|
|
of the objects, while the efficiency of solution 2
|
|
|
|
depends on the values of the objects.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
\section{Edit distance}
|
|
|
|
|
|
|
|
\index{edit distance}
|
|
|
|
\index{Levenshtein distance}
|
|
|
|
|
2017-02-25 15:51:29 +01:00
|
|
|
The \key{edit distance} or \key{Levenshtein distance}\footnote{The distance
|
2017-02-27 20:29:32 +01:00
|
|
|
is named after V. I. Levenshtein who studied it in connection with binary codes \cite{lev66}.}
|
2017-01-31 23:08:52 +01:00
|
|
|
is the minimum number of editing operations
|
2017-02-13 22:16:30 +01:00
|
|
|
needed to transform a string
|
|
|
|
into another string.
|
2017-01-02 23:46:18 +01:00
|
|
|
The allowed editing operations are as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{itemize}
|
2017-01-02 23:46:18 +01:00
|
|
|
\item insert a character (e.g. \texttt{ABC} $\rightarrow$ \texttt{ABCA})
|
|
|
|
\item remove a character (e.g. \texttt{ABC} $\rightarrow$ \texttt{AC})
|
|
|
|
\item change a character (e.g. \texttt{ABC} $\rightarrow$ \texttt{ADC})
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{itemize}
|
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
For example, the edit distance between
|
2017-02-13 22:16:30 +01:00
|
|
|
\texttt{LOVE} and \texttt{MOVIE} is 2,
|
|
|
|
because we can first perform the operation
|
2017-01-02 23:46:18 +01:00
|
|
|
\texttt{LOVE} $\rightarrow$ \texttt{MOVE}
|
2017-02-13 22:16:30 +01:00
|
|
|
(change) and then the operation
|
2017-01-02 23:46:18 +01:00
|
|
|
\texttt{MOVE} $\rightarrow$ \texttt{MOVIE}
|
|
|
|
(insertion).
|
2017-01-31 23:08:52 +01:00
|
|
|
This is the smallest possible number of operations,
|
|
|
|
because it is clear that it is not possible
|
|
|
|
to use only one operation.
|
2017-01-02 23:46:18 +01:00
|
|
|
|
|
|
|
Suppose we are given strings
|
2017-01-31 23:08:52 +01:00
|
|
|
\texttt{x} and \texttt{y} that contain
|
|
|
|
$n$ and $m$ characters, respectively,
|
|
|
|
and we wish to calculate the edit distance
|
2017-01-02 23:46:18 +01:00
|
|
|
between them.
|
2017-02-13 22:16:30 +01:00
|
|
|
This can be done using
|
2017-01-02 23:46:18 +01:00
|
|
|
dynamic programming in $O(nm)$ time.
|
|
|
|
Let $f(a,b)$ denote the edit distance
|
|
|
|
between the first $a$ characters of \texttt{x}
|
|
|
|
and the first $b$ characters of \texttt{y}.
|
|
|
|
Using this function, the edit distance between
|
2017-01-31 23:08:52 +01:00
|
|
|
\texttt{x} and \texttt{y} equals $f(n,m)$.
|
2017-01-02 23:46:18 +01:00
|
|
|
|
|
|
|
The base cases for the function are
|
2016-12-28 23:54:51 +01:00
|
|
|
\[
|
|
|
|
\begin{array}{lcl}
|
|
|
|
f(0,b) & = & b \\
|
|
|
|
f(a,0) & = & a \\
|
|
|
|
\end{array}
|
|
|
|
\]
|
2017-01-02 23:46:18 +01:00
|
|
|
and in the general case the formula is
|
2016-12-28 23:54:51 +01:00
|
|
|
\[ f(a,b) = \min(f(a,b-1)+1,f(a-1,b)+1,f(a-1,b-1)+c),\]
|
2017-01-02 23:46:18 +01:00
|
|
|
where $c=0$ if the $a$th character of \texttt{x}
|
|
|
|
equals the $b$th character of \texttt{y},
|
|
|
|
and otherwise $c=1$.
|
2017-02-13 22:16:30 +01:00
|
|
|
The formula considers all possible ways to shorten the strings:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{itemize}
|
2017-01-02 23:46:18 +01:00
|
|
|
\item $f(a,b-1)$ means that a character is inserted to \texttt{x}
|
2017-01-31 23:08:52 +01:00
|
|
|
\item $f(a-1,b)$ means that a character is removed from \texttt{x}
|
2017-01-02 23:46:18 +01:00
|
|
|
\item $f(a-1,b-1)$ means that \texttt{x} and \texttt{y} contain
|
|
|
|
the same character ($c=0$),
|
|
|
|
or a character in \texttt{x} is transformed into
|
|
|
|
a character in \texttt{y} ($c=1$)
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{itemize}
|
2017-01-02 23:46:18 +01:00
|
|
|
The following table shows the values of $f$
|
|
|
|
in the example case:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=.65]
|
|
|
|
\begin{scope}
|
|
|
|
%\fill [color=lightgray] (5, -3) rectangle (6, -4);
|
|
|
|
\draw (1, -1) grid (7, -6);
|
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
\node at (0.5,-2.5) {\texttt{L}};
|
|
|
|
\node at (0.5,-3.5) {\texttt{O}};
|
|
|
|
\node at (0.5,-4.5) {\texttt{V}};
|
|
|
|
\node at (0.5,-5.5) {\texttt{E}};
|
2016-12-28 23:54:51 +01:00
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
\node at (2.5,-0.5) {\texttt{M}};
|
|
|
|
\node at (3.5,-0.5) {\texttt{O}};
|
|
|
|
\node at (4.5,-0.5) {\texttt{V}};
|
|
|
|
\node at (5.5,-0.5) {\texttt{I}};
|
|
|
|
\node at (6.5,-0.5) {\texttt{E}};
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\node at (1.5,-1.5) {$0$};
|
|
|
|
\node at (1.5,-2.5) {$1$};
|
|
|
|
\node at (1.5,-3.5) {$2$};
|
|
|
|
\node at (1.5,-4.5) {$3$};
|
|
|
|
\node at (1.5,-5.5) {$4$};
|
|
|
|
\node at (2.5,-1.5) {$1$};
|
|
|
|
\node at (2.5,-2.5) {$1$};
|
|
|
|
\node at (2.5,-3.5) {$2$};
|
|
|
|
\node at (2.5,-4.5) {$3$};
|
|
|
|
\node at (2.5,-5.5) {$4$};
|
|
|
|
\node at (3.5,-1.5) {$2$};
|
|
|
|
\node at (3.5,-2.5) {$2$};
|
|
|
|
\node at (3.5,-3.5) {$1$};
|
|
|
|
\node at (3.5,-4.5) {$2$};
|
|
|
|
\node at (3.5,-5.5) {$3$};
|
|
|
|
\node at (4.5,-1.5) {$3$};
|
|
|
|
\node at (4.5,-2.5) {$3$};
|
|
|
|
\node at (4.5,-3.5) {$2$};
|
|
|
|
\node at (4.5,-4.5) {$1$};
|
|
|
|
\node at (4.5,-5.5) {$2$};
|
|
|
|
\node at (5.5,-1.5) {$4$};
|
|
|
|
\node at (5.5,-2.5) {$4$};
|
|
|
|
\node at (5.5,-3.5) {$3$};
|
|
|
|
\node at (5.5,-4.5) {$2$};
|
|
|
|
\node at (5.5,-5.5) {$2$};
|
|
|
|
\node at (6.5,-1.5) {$5$};
|
|
|
|
\node at (6.5,-2.5) {$5$};
|
|
|
|
\node at (6.5,-3.5) {$4$};
|
|
|
|
\node at (6.5,-4.5) {$3$};
|
|
|
|
\node at (6.5,-5.5) {$2$};
|
|
|
|
\end{scope}
|
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
The lower-right corner of the table
|
2017-02-13 22:16:30 +01:00
|
|
|
tells us that the edit distance between
|
2017-01-02 23:46:18 +01:00
|
|
|
\texttt{LOVE} and \texttt{MOVIE} is 2.
|
|
|
|
The table also shows how to construct
|
|
|
|
the shortest sequence of editing operations.
|
|
|
|
In this case the path is as follows:
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=.65]
|
|
|
|
\begin{scope}
|
|
|
|
\draw (1, -1) grid (7, -6);
|
|
|
|
|
2017-01-02 23:46:18 +01:00
|
|
|
\node at (0.5,-2.5) {\texttt{L}};
|
|
|
|
\node at (0.5,-3.5) {\texttt{O}};
|
|
|
|
\node at (0.5,-4.5) {\texttt{V}};
|
|
|
|
\node at (0.5,-5.5) {\texttt{E}};
|
|
|
|
|
|
|
|
\node at (2.5,-0.5) {\texttt{M}};
|
|
|
|
\node at (3.5,-0.5) {\texttt{O}};
|
|
|
|
\node at (4.5,-0.5) {\texttt{V}};
|
|
|
|
\node at (5.5,-0.5) {\texttt{I}};
|
|
|
|
\node at (6.5,-0.5) {\texttt{E}};
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
\node at (1.5,-1.5) {$0$};
|
|
|
|
\node at (1.5,-2.5) {$1$};
|
|
|
|
\node at (1.5,-3.5) {$2$};
|
|
|
|
\node at (1.5,-4.5) {$3$};
|
|
|
|
\node at (1.5,-5.5) {$4$};
|
|
|
|
\node at (2.5,-1.5) {$1$};
|
|
|
|
\node at (2.5,-2.5) {$1$};
|
|
|
|
\node at (2.5,-3.5) {$2$};
|
|
|
|
\node at (2.5,-4.5) {$3$};
|
|
|
|
\node at (2.5,-5.5) {$4$};
|
|
|
|
\node at (3.5,-1.5) {$2$};
|
|
|
|
\node at (3.5,-2.5) {$2$};
|
|
|
|
\node at (3.5,-3.5) {$1$};
|
|
|
|
\node at (3.5,-4.5) {$2$};
|
|
|
|
\node at (3.5,-5.5) {$3$};
|
|
|
|
\node at (4.5,-1.5) {$3$};
|
|
|
|
\node at (4.5,-2.5) {$3$};
|
|
|
|
\node at (4.5,-3.5) {$2$};
|
|
|
|
\node at (4.5,-4.5) {$1$};
|
|
|
|
\node at (4.5,-5.5) {$2$};
|
|
|
|
\node at (5.5,-1.5) {$4$};
|
|
|
|
\node at (5.5,-2.5) {$4$};
|
|
|
|
\node at (5.5,-3.5) {$3$};
|
|
|
|
\node at (5.5,-4.5) {$2$};
|
|
|
|
\node at (5.5,-5.5) {$2$};
|
|
|
|
\node at (6.5,-1.5) {$5$};
|
|
|
|
\node at (6.5,-2.5) {$5$};
|
|
|
|
\node at (6.5,-3.5) {$4$};
|
|
|
|
\node at (6.5,-4.5) {$3$};
|
|
|
|
\node at (6.5,-5.5) {$2$};
|
|
|
|
|
|
|
|
\path[draw=red,thick,-,line width=2pt] (6.5,-5.5) -- (5.5,-4.5);
|
|
|
|
\path[draw=red,thick,-,line width=2pt] (5.5,-4.5) -- (4.5,-4.5);
|
|
|
|
\path[draw=red,thick,->,line width=2pt] (4.5,-4.5) -- (1.5,-1.5);
|
|
|
|
\end{scope}
|
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
|
|
|
|
2017-01-03 00:07:43 +01:00
|
|
|
The last characters of \texttt{LOVE} and \texttt{MOVIE}
|
|
|
|
are equal, so the edit distance between them
|
|
|
|
equals the edit distance between \texttt{LOV} and \texttt{MOVI}.
|
|
|
|
We can use one editing operation to remove the
|
|
|
|
character \texttt{I} from \texttt{MOVI}.
|
|
|
|
Thus, the edit distance is one larger than
|
|
|
|
the edit distance between \texttt{LOV} and \texttt{MOV}, etc.
|
|
|
|
|
2017-01-03 01:32:39 +01:00
|
|
|
\section{Counting tilings}
|
2017-01-03 00:07:43 +01:00
|
|
|
|
2017-02-13 22:16:30 +01:00
|
|
|
Sometimes the states of a dynamic programming solution
|
2017-01-31 23:08:52 +01:00
|
|
|
are more complex than fixed combinations of numbers.
|
2017-01-03 00:07:43 +01:00
|
|
|
As an example,
|
2017-01-31 23:08:52 +01:00
|
|
|
we consider the problem of calculating
|
|
|
|
the number of distinct ways to
|
2017-01-03 00:07:43 +01:00
|
|
|
fill an $n \times m$ grid using
|
|
|
|
$1 \times 2$ and $2 \times 1$ size tiles.
|
|
|
|
For example, one valid solution
|
|
|
|
for the $4 \times 7$ grid is
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{center}
|
|
|
|
\begin{tikzpicture}[scale=.65]
|
|
|
|
\draw (0,0) grid (7,4);
|
|
|
|
\draw[fill=gray] (0+0.2,0+0.2) rectangle (2-0.2,1-0.2);
|
|
|
|
\draw[fill=gray] (2+0.2,0+0.2) rectangle (4-0.2,1-0.2);
|
|
|
|
\draw[fill=gray] (4+0.2,0+0.2) rectangle (6-0.2,1-0.2);
|
|
|
|
\draw[fill=gray] (0+0.2,1+0.2) rectangle (2-0.2,2-0.2);
|
|
|
|
\draw[fill=gray] (2+0.2,1+0.2) rectangle (4-0.2,2-0.2);
|
|
|
|
\draw[fill=gray] (1+0.2,2+0.2) rectangle (3-0.2,3-0.2);
|
|
|
|
\draw[fill=gray] (1+0.2,3+0.2) rectangle (3-0.2,4-0.2);
|
|
|
|
\draw[fill=gray] (4+0.2,3+0.2) rectangle (6-0.2,4-0.2);
|
|
|
|
|
|
|
|
\draw[fill=gray] (0+0.2,2+0.2) rectangle (1-0.2,4-0.2);
|
|
|
|
\draw[fill=gray] (3+0.2,2+0.2) rectangle (4-0.2,4-0.2);
|
|
|
|
\draw[fill=gray] (6+0.2,2+0.2) rectangle (7-0.2,4-0.2);
|
|
|
|
\draw[fill=gray] (4+0.2,1+0.2) rectangle (5-0.2,3-0.2);
|
|
|
|
\draw[fill=gray] (5+0.2,1+0.2) rectangle (6-0.2,3-0.2);
|
|
|
|
\draw[fill=gray] (6+0.2,0+0.2) rectangle (7-0.2,2-0.2);
|
|
|
|
|
|
|
|
\end{tikzpicture}
|
|
|
|
\end{center}
|
2017-01-03 00:07:43 +01:00
|
|
|
and the total number of solutions is 781.
|
|
|
|
|
|
|
|
The problem can be solved using dynamic programming
|
|
|
|
by going through the grid row by row.
|
|
|
|
Each row in a solution can be represented as a
|
|
|
|
string that contains $m$ characters from the set
|
|
|
|
$\{\sqcap, \sqcup, \sqsubset, \sqsupset \}$.
|
|
|
|
For example, the above solution consists of four rows
|
|
|
|
that correspond to the following strings:
|
2016-12-28 23:54:51 +01:00
|
|
|
\begin{itemize}
|
|
|
|
\item
|
2017-01-03 00:07:43 +01:00
|
|
|
$\sqcap \sqsubset \sqsupset \sqcap \sqsubset \sqsupset \sqcap$
|
2016-12-28 23:54:51 +01:00
|
|
|
\item
|
2017-01-03 00:07:43 +01:00
|
|
|
$\sqcup \sqsubset \sqsupset \sqcup \sqcap \sqcap \sqcup$
|
2016-12-28 23:54:51 +01:00
|
|
|
\item
|
2017-01-03 00:07:43 +01:00
|
|
|
$\sqsubset \sqsupset \sqsubset \sqsupset \sqcup \sqcup \sqcap$
|
2016-12-28 23:54:51 +01:00
|
|
|
\item
|
2017-01-03 00:07:43 +01:00
|
|
|
$\sqsubset \sqsupset \sqsubset \sqsupset \sqsubset \sqsupset \sqcup$
|
2016-12-28 23:54:51 +01:00
|
|
|
\end{itemize}
|
|
|
|
|
2017-01-03 00:07:43 +01:00
|
|
|
Let $f(k,x)$ denote the number of ways to
|
2017-01-31 23:08:52 +01:00
|
|
|
construct a solution for rows $1 \ldots k$
|
2017-01-03 00:07:43 +01:00
|
|
|
in the grid so that string $x$ corresponds to row $k$.
|
2017-02-27 20:29:32 +01:00
|
|
|
It is possible to use dynamic programming here,
|
2017-01-03 00:07:43 +01:00
|
|
|
because the state of a row is constrained
|
2017-01-31 23:08:52 +01:00
|
|
|
only by the state of the previous row.
|
2017-01-03 00:07:43 +01:00
|
|
|
|
2017-01-31 23:08:52 +01:00
|
|
|
A solution is valid if row $1$ does not contain
|
2017-01-03 00:07:43 +01:00
|
|
|
the character $\sqcup$,
|
2017-01-31 23:08:52 +01:00
|
|
|
row $n$ does not contain the character $\sqcap$,
|
|
|
|
and all consecutive rows are \emph{compatible}.
|
2017-01-03 00:07:43 +01:00
|
|
|
For example, the rows
|
|
|
|
$\sqcup \sqsubset \sqsupset \sqcup \sqcap \sqcap \sqcup$ and
|
2016-12-28 23:54:51 +01:00
|
|
|
$\sqsubset \sqsupset \sqsubset \sqsupset \sqcup \sqcup \sqcap$
|
2017-01-03 00:07:43 +01:00
|
|
|
are compatible, while the rows
|
|
|
|
$\sqcap \sqsubset \sqsupset \sqcap \sqsubset \sqsupset \sqcap$ and
|
2016-12-28 23:54:51 +01:00
|
|
|
$\sqsubset \sqsupset \sqsubset \sqsupset \sqsubset \sqsupset \sqcup$
|
2017-01-03 00:07:43 +01:00
|
|
|
are not compatible.
|
|
|
|
|
|
|
|
Since a row consists of $m$ characters and there are
|
2017-01-31 23:08:52 +01:00
|
|
|
four choices for each character, the number of distinct
|
2017-01-03 00:07:43 +01:00
|
|
|
rows is at most $4^m$.
|
|
|
|
Thus, the time complexity of the solution is
|
2017-01-31 23:08:52 +01:00
|
|
|
$O(n 4^{2m})$ because we can go through the
|
2017-01-03 00:07:43 +01:00
|
|
|
$O(4^m)$ possible states for each row,
|
|
|
|
and for each state, there are $O(4^m)$
|
|
|
|
possible states for the previous row.
|
2017-01-31 23:08:52 +01:00
|
|
|
In practice, it is a good idea to rotate the grid
|
|
|
|
so that the shorter side has length $m$,
|
2017-01-03 00:07:43 +01:00
|
|
|
because the factor $4^{2m}$ dominates the time complexity.
|
|
|
|
|
|
|
|
It is possible to make the solution more efficient
|
2017-02-27 20:29:32 +01:00
|
|
|
by using a more compact representation for the rows.
|
2017-02-13 22:16:30 +01:00
|
|
|
It turns out that it is sufficient to know which
|
|
|
|
columns of the previous row contain the upper square
|
2017-01-03 00:07:43 +01:00
|
|
|
of a vertical tile.
|
|
|
|
Thus, we can represent a row using only characters
|
2017-01-31 23:08:52 +01:00
|
|
|
$\sqcap$ and $\Box$, where $\Box$ is a combination
|
2017-01-03 00:07:43 +01:00
|
|
|
of characters
|
|
|
|
$\sqcup$, $\sqsubset$ and $\sqsupset$.
|
2017-01-31 23:08:52 +01:00
|
|
|
Using this representation, there are only
|
|
|
|
$2^m$ distinct rows and the time complexity is
|
2017-01-03 00:07:43 +01:00
|
|
|
$O(n 2^{2m})$.
|
|
|
|
|
|
|
|
As a final note, there is also a surprising direct formula
|
2017-04-18 19:31:08 +02:00
|
|
|
for calculating the number of tilings\footnote{Surprisingly,
|
|
|
|
this formula was discovered in 1961 by two research teams \cite{kas61,tem61}
|
|
|
|
that worked independently.}:
|
2017-01-31 23:08:52 +01:00
|
|
|
\[ \prod_{a=1}^{\lceil n/2 \rceil} \prod_{b=1}^{\lceil m/2 \rceil} 4 \cdot (\cos^2 \frac{\pi a}{n + 1} + \cos^2 \frac{\pi b}{m+1})\]
|
|
|
|
This formula is very efficient, because it calculates
|
|
|
|
the number of tilings in $O(nm)$ time,
|
2017-01-03 00:07:43 +01:00
|
|
|
but since the answer is a product of real numbers,
|
|
|
|
a practical problem in using the formula is
|
|
|
|
how to store the intermediate results accurately.
|
2016-12-28 23:54:51 +01:00
|
|
|
|
|
|
|
|