Revision continues

This commit is contained in:
Antti H S Laaksonen 2017-05-25 12:36:59 +03:00
parent ace73e0bf5
commit 0b998fc638
1 changed files with 138 additions and 103 deletions

View File

@ -492,20 +492,35 @@ contains two such subgrids:
There is an $O(n^3)$ time algorithm for solving the problem:
go through all $O(n^2)$ pairs of rows and for each pair
calculate the number of columns that contain a black
$(a,b)$ calculate the number of columns that contain a black
square in both rows in $O(n)$ time.
Then, if there are $c$ such columns for a fixed row pair,
they account for $c(c-1)/2$ subgrids with black corners.
The following code assumes that $\texttt{color}[y][x]$
denotes the color in row $y$ in column $x$:
\begin{lstlisting}
int count = 0;
for (int i = 0; i < n; i++) {
if (color[a][i] == BLACK && color[b][i] == BLACK) count++;
}
\end{lstlisting}
Then, those columns
account for $\texttt{count}(\texttt{count}-1)/2$ subgrids with black corners,
because we can choose any two of them to form a subgrid.
To optimize this algorithm, we divide the grid into blocks
of columns such that each block consists of $N$
consecutive columns. Then, each row is stored as
a list of $N$-bit numbers that describe the colors
of the squares.
Using this representation,
we can process $N$ columns at the same time
using bit operations, and the resulting algorithm
works in $O(n^3/N)$ time.
of the squares. Now we can process $N$ columns at the same time
using bit operations. In the following code,
$\texttt{color}[y][k]$ represents
a block of $N$ colors as bits (0 is white and 1 is black).
\begin{lstlisting}
int count = 0;
for (int i = 0; i <= n/N; i++) {
count += __builtin_popcount(color[a][i]&color[b][i]);
}
\end{lstlisting}
The resulting algorithm works in $O(n^3/N)$ time.
We generated a random grid of size $2500 \times 2500$
and compared the original and bit-optimized implementation.
@ -520,110 +535,109 @@ Bit operations provide an efficient and convenient
way to implement dynamic programming algorithms
whose states contain subsets of elements,
because such states can be stored as integers.
Next we discuss some examples of combining
Next we discuss examples of combining
bit operations and dynamic programming.
\subsubsection{Paths in a grid}
\subsubsection{Optimal selection}
As a first example, consider
an $n \times n$ grid where
each square contains an integer.
Our task is to check if there is a path from the upper-left
corner to the lower-right corner
such that we only move right and down
and each integer between 0 and $k$ appears on the path.
For example, in the following grid,
there is a path that contains all
integers between 0 and $k$:
As a first example, consider the following problem:
We are given the prices of $k$ products
over $n$ days, and we want to buy each product
exactly once.
However, we are allowed to buy at most one product
in a day.
What is the minimum total price?
For example, consider the following scenario ($k=3$ and $n=8$):
\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) {2};
\node at (1.5,8.5) {0};
\node at (2.5,8.5) {3};
\node at (3.5,8.5) {1};
\node at (4.5,8.5) {1};
\node at (0.5,7.5) {0};
\node at (1.5,7.5) {1};
\node at (2.5,7.5) {4};
\node at (3.5,7.5) {2};
\node at (4.5,7.5) {0};
\node at (0.5,6.5) {3};
\node at (1.5,6.5) {0};
\node at (2.5,6.5) {2};
\node at (3.5,6.5) {4};
\node at (4.5,6.5) {1};
\node at (0.5,5.5) {2};
\node at (1.5,5.5) {1};
\node at (2.5,5.5) {0};
\node at (3.5,5.5) {1};
\node at (4.5,5.5) {3};
\node at (0.5,4.5) {0};
\node at (1.5,4.5) {2};
\node at (2.5,4.5) {3};
\node at (3.5,4.5) {3};
\node at (4.5,4.5) {1};
\end{scope}
\draw (0, 0) grid (8,3);
\node at (-2.5,2.5) {product $A$};
\node at (-2.5,1.5) {product $B$};
\node at (-2.5,0.5) {product $C$};
\foreach \x/\v in {0/6,1/9,2/5,3/2,4/8,5/9,6/1,7/6}
{\node at (\x+0.5,2.5) {\v};}
\foreach \x/\v in {0/8,1/2,2/6,3/2,4/7,5/5,6/7,7/2}
{\node at (\x+0.5,1.5) {\v};}
\foreach \x/\v in {0/5,1/3,2/9,3/7,4/3,5/5,6/1,7/4}
{\node at (\x+0.5,0.5) {\v};}
\end{tikzpicture}
\end{center}
In this scenario, the minimum total price is $5$:
\begin{center}
\begin{tikzpicture}[scale=.65]
\fill [color=lightgray] (1, 1) rectangle (2, 2);
\fill [color=lightgray] (3, 2) rectangle (4, 3);
\fill [color=lightgray] (6, 0) rectangle (7, 1);
\draw (0, 0) grid (8,3);
\node at (-2.5,2.5) {product $A$};
\node at (-2.5,1.5) {product $B$};
\node at (-2.5,0.5) {product $C$};
\foreach \x/\v in {0/6,1/9,2/5,3/2,4/8,5/9,6/1,7/6}
{\node at (\x+0.5,2.5) {\v};}
\foreach \x/\v in {0/8,1/2,2/6,3/2,4/7,5/5,6/7,7/2}
{\node at (\x+0.5,1.5) {\v};}
\foreach \x/\v in {0/5,1/3,2/9,3/7,4/3,5/5,6/1,7/4}
{\node at (\x+0.5,0.5) {\v};}
\end{tikzpicture}
\end{center}
We assume that the rows and columns are numbered
from 1 to $n$, and $\texttt{value}[x][y]$
denotes the value at position $(x,y)$.
It turns out that the problem can be solved in $O(n^2 2^k)$ time
using dynamic programming.
Let $\texttt{price}[x][d]$ denote the price of product $x$
on day $d$ where $0 \le x < k$ and $0 \le d < n$.
For example, in the above scenario $\texttt{price}[2][3] = 7$.
Then, let $\texttt{total}(S,d)$ denote the minimum total
price for buying a subset $S$ of products by day $d$.
Using this function, the solution to the problem is
$\texttt{total}(\{0 \ldots k-1\},n-1)$.
Let $\texttt{possible}(x,y,S)$ be a function
that indicates whether there is a path
from the upper-left square to square $(x,y)$ such that
all values in $S$ appear on the path.
Thus, $\texttt{possible}(n,n,\{0 \ldots k\})$
tells us whether a desired path exists.
The values of the function can be calculated using
the following recurrence:
First, $\texttt{total}(\emptyset,d) = 0$,
because it does not cost anything to buy an empty set,
and $\texttt{total}(\{x\},0) = \texttt{price}[x][0]$,
because there is one way to buy one product on the first day.
Then, the following recurrence can be used:
\begin{equation*}
\begin{aligned}
\texttt{possible}(x,y,\emptyset) & = & \textrm{true} \\
\texttt{possible}(x,y,S) & = & \texttt{possible}(x-1,y,S \setminus \texttt{value}[x][y]) \lor \\
& & \texttt{possible}(x,y-1,S \setminus \texttt{value}[x][y]) \\
\texttt{total}(S,d) & = \min( & \texttt{total}(S,d-1), \\
& & \min_{x \in S} \texttt{total}(S \setminus x,d-1)+\texttt{price}[x][d]))
\end{aligned}
\end{equation*}
The base case states that there is always a path that
does not contain any integers.
Then, in the recursive case we consider both directions
and remove $\texttt{value}[x][y]$
from $S$, because we can collect it in square $(x,y)$.
This means that we either do not buy any product on day $d$
or buy a product $x$ that belongs to $S$.
In the latter case, we remove $x$ from $S$ and add the
price of $x$ to the total price.
We can implement the dynamic programming using an array
The next step is to calculate the values of the function
using dynamic programming.
To store the function values, we declare an array
\begin{lstlisting}
bool possible[N][N][1<<K];
int total[1<<K][N];
\end{lstlisting}
where $N$ and $K$ are suitably large constants.
Then, we can calculate the values
of the function as follows:
where $K$ and $N$ are suitably large constants.
The first dimension of the array corresponds to a bit
representation of a subset.
First, the cases where $d=0$ can be processed as follows:
\begin{lstlisting}
for (int x = 0; x <= n; x++) {
for (int y = 0; y <= n; y++) {
possible[x][y][0] = true;
if (x == 0 || y == 0) continue;
for (int b = 1; b < (1<<k); b++) {
possible[x][y][b] = possible[x-1][y][b&~value[x][y]] ||
possible[x][y-1][b&~value[x][y]];
for (int x = 0; x < k; x++) {
total[1<<x][0] = price[x][0];
}
\end{lstlisting}
Then, the recurrence translates into the following code:
\begin{lstlisting}
for (int d = 1; d < n; d++) {
for (int s = 0; s < (1<<k); s++) {
total[s][d] = total[s][d-1];
for (int x = 0; x < k; x++) {
if (s&(1<<x)) {
total[s][d] = min(total[s][d],
total[s^(1<<x)][d-1]+price[x][d]);
}
}
}
}
\end{lstlisting}
The time complexity of the algorithm is $O(n 2^k k)$.
\subsubsection{From permutations to subsets}
@ -631,14 +645,14 @@ Using dynamic programming, it is often possible
to change an iteration over permutations into
an iteration over subsets\footnote{This technique was introduced in 1962
by M. Held and R. M. Karp \cite{hel62}.}.
The benefit of this technique is that
The benefit of this is that
$n!$, the number of permutations of an $n$ element set,
is much larger than $2^n$, the number of subsets
of the same set.
For example, if $n=20$, then
$n! \approx 2.4 \cdot 10^{18}$ and $2^n \approx 10^6$.
Thus, for certain values of $n$,
we can efficiently go through subsets but not through permutations.
we can efficiently go through the subsets but not through the permutations.
As an example, consider the following problem:
There is an elevator with maximum weight $x$,
@ -675,25 +689,25 @@ The idea is to calculate for each subset of people
two values: the minimum number of rides needed and
the minimum weight of people who ride in the last group.
Let $\texttt{rides}(X)$ and $\texttt{weight}(X)$ denote
Let $\texttt{rides}(S)$ and $\texttt{weight}(S)$ denote
the minimum number of rides and the minimum
weight of the last group, where $X$ is a subset
weight of the last group, where $S$ is a subset
of people. For example,
\[ \texttt{rides}(\{B,D,E\})=2 \hspace{10px} \textrm{and}
\hspace{10px} \texttt{weight}(\{B,D,E\})=5,\]
because the optimal rides are $\{B,E\}$ and $\{D\}$,
and the second ride has weight 5.
Of course, our final goal is to calculate the value
of $\texttt{rides}(\{A,B,C,D,E\})$ that is the solution
to the problem.
of $\texttt{rides}(X)$ where $X$ is a set that
contains all the people.
We can calculate the values
of the functions recursively and then apply
dynamic programming.
The idea is to go through all people
that belong to $X$ and optimally
who belong to $S$ and optimally
choose the last person who enters the elevator.
For example, if $X=\{B,D,E\}$,
For example, if $S=\{B,D,E\}$,
one of $B$, $D$ and $E$ is the last person
who enters the elevator.
Each such choice yields a subproblem
@ -706,19 +720,40 @@ for (int b = 0; b < (1<<n); b++) {
}
\end{lstlisting}
to go through the subsets,
because if $X$ and $Y$ are two subsets
and $X \subset Y$,
then $X$ comes before $Y$ in the above order.
because if $S_1$ and $S_2$ are two subsets
and $S_1 \subset S_2$,
then $S_1$ comes before $S_2$ in the above order.
\subsubsection{Counting subsets}
Our last problem in this chapter is as follows:
Let $X=\{0,1,\ldots,n-1\}$, and each subset $S \subset X$,
Let $X=\{0 \ldots n-1\}$, and each subset $S \subset X$,
is assigned an integer $\texttt{value}(S)$.
Our task is to calculate for each $S$
\[\texttt{sum}(S) = \sum_{A \subset S} \texttt{value}(A),\]
i.e., the sum of values of subsets of $S$.
For example, suppose that $n=3$ and the values are as follows:
\begin{multicols}{2}
\begin{itemize}
\item $\texttt{value}(\emptyset) = 3$
\item $\texttt{value}(\{0\}) = 1$
\item $\texttt{value}(\{1\}) = 4$
\item $\texttt{value}(\{0,1\}) = 5$
\item $\texttt{value}(\{2\}) = 5$
\item $\texttt{value}(\{0,2\}) = 1$
\item $\texttt{value}(\{1,2\}) = 3$
\item $\texttt{value}(\{0,1,2\}) = 3$
\end{itemize}
\end{multicols}
In this case, for example,
\begin{equation*}
\begin{split}
\texttt{sum}(\{0,2\}) &= \texttt{value}(\emptyset)+\texttt{value}(\{0\})+\texttt{value}(\{2\})+\texttt{value}(\{0,2\}) \\
&= 3 + 1 + 5 + 1 = 10.
\end{split}
\end{equation*}
Because there are a total of $2^n$ subsets,
one possible solution is to go through all
pairs of subsets in $O(2^{2n})$ time.