diff --git a/chapter10.tex b/chapter10.tex index b961cad..e513d9d 100644 --- a/chapter10.tex +++ b/chapter10.tex @@ -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<