diff --git a/luku05.tex b/luku05.tex index 02429ef..d90120e 100644 --- a/luku05.tex +++ b/luku05.tex @@ -2,7 +2,7 @@ \key{Complete search} is a general method that can be used -for solving almost any algorithm problem. +to solve almost any algorithm problem. The idea is to generate all possible solutions to the problem using brute force, and then select the best solution or count the @@ -30,7 +30,7 @@ or use bit operations of integers. An elegant way to go through all subsets of a set is to use recursion. -The following function \texttt{gen} +The following function generates the subsets of the set $\{1,2,\ldots,n\}$. The function maintains a vector @@ -128,8 +128,8 @@ which corresponds to an integer between $0 \ldots 2^n-1$. The ones in the bit sequence indicate which elements are included in the subset. -The usual convention is that element $k$ -is included in the subset if the $k$th last bit +The usual convention is that the $k$th element +is included in the subset exactly when the $k$th last bit in the sequence is one. For example, the bit representation of 25 is 11001, that corresponds to the subset $\{1,4,5\}$. @@ -143,8 +143,8 @@ for (int b = 0; b < (1<b$, so we should swap the tasks: \end{scope} \end{tikzpicture} \end{center} -Now $X$ gives $b$ points less and $Y$ gives $a$ points more, +Now $X$ gives $b$ points fewer and $Y$ gives $a$ points more, so the total score increases by $a-b > 0$. In an optimal solution, for any two consecutive tasks, @@ -470,7 +470,7 @@ which means that the length of each codeword is the same. For example, we can compress the string \texttt{AABACDACA} as follows: -\[000001001011001000\] +\[00\,00\,01\,00\,10\,11\,00\,10\,00\] Using this code, the length of the compressed string is 18 bits. However, we can compress the string better @@ -496,7 +496,7 @@ An optimal code produces a compressed string that is as short as possible. In this case, the compressed string using the optimal code is -\[001100101110100,\] +\[0\,0\,110\,0\,10\,111\,0\,10\,0,\] so only 15 bits are needed instead of 18 bits. Thus, thanks to a better code it was possible to save 3 bits in the compressed string. @@ -539,12 +539,12 @@ in the string, and each character's codeword can be read by following a path from the root to the corresponding node. -A move to the left correspons to bit 0, +A move to the left corresponds to bit 0, and a move to the right corresponds to bit 1. Initially, each character of the string is represented by a node whose weight is the -number of times the character appears in the string. +number of times the character occurs in the string. Then at each step two nodes with minimum weights are combined by creating a new node whose weight is the sum of the weights diff --git a/luku07.tex b/luku07.tex index c8c7667..6168709 100644 --- a/luku07.tex +++ b/luku07.tex @@ -14,7 +14,7 @@ There are two uses for dynamic programming: \begin{itemize} \item -\key{Findind an optimal solution}: +\key{Finding an optimal solution}: We want to find a solution that is as large as possible or as small as possible. \item @@ -24,14 +24,14 @@ possible solutions. \end{itemize} We will first see how dynamic programming can -be used for finding an optimal solution, +be used to find an optimal solution, and then we will use the same idea for counting the solutions. Understanding dynamic programming is a milestone in every competitive programmer's career. While the basic idea of the technique is simple, -the challenge is how to apply it for different problems. +the challenge is how to apply it to different problems. This chapter introduces a set of classic problems that are a good starting point. @@ -47,7 +47,7 @@ In Chapter 6, we solved the problem using a greedy algorithm that always selects the largest possible coin. The greedy algorithm works, for example, -when coins are euro coins, +when the coins are the euro coins, but in the general case the greedy algorithm does not necessarily produce an optimal solution. @@ -60,15 +60,15 @@ that goes through all possibilities how to form the sum, like a brute force algorithm. However, the dynamic programming algorithm is efficient because -it uses \emph{memoization} to -calculate the answer to each subproblem only once. +it uses \emph{memoization} and +calculates the answer to each subproblem only once. \subsubsection{Recursive formulation} The idea in dynamic programming is to formulate the problem recursively so that the answer to the problem can be -calculated from the answers for smaller +calculated from answers to smaller subproblems. In the coin problem, a natural recursive problem is as follows: @@ -107,8 +107,8 @@ and $f(5)=2$ because the sum 5 can be formed using coins 1 and 4. The essential property in the function is -that the value $f(x)$ can be calculated -recursively from the smaller values of the function. +that each value of $f(x)$ can be calculated +recursively from smaller values of the function. For example, if the coin set is $\{1,3,4\}$, there are three ways to select the first coin in a solution: we can choose coin 1, 3 or 4. @@ -133,9 +133,8 @@ In addition, it is convenient to define \[f(x)=\infty\hspace{8px}\textrm{if $x<0$}.\] This means that an infinite number of coins is needed for forming a negative sum of money. -This prevents the situation that the recursive -function would form a solution where the -initial sum of money is negative. +This prevents the function from constructing +a solution where the initial sum of money is negative. Once a recursive function that solves the problem has been found, @@ -159,7 +158,7 @@ and the value $10^9$ denotes infinity. This function works but it is not efficient yet, because it goes through a large number of ways to construct the sum. -However, the function becomes efficient by +However, the function can be made efficient by using memoization. \subsubsection{Memoization} @@ -170,20 +169,20 @@ Dynamic programming allows us to calculate the value of a recursive function efficiently using \key{memoization}. This means that an auxiliary array is used -for storing the values of the function +for recording the values of the function for different parameters. For each parameter, the value of the function is calculated recursively only once, and after this, the value can be directly retrieved from the array. -In this problem, we can use the array +In this problem, we can use an array \begin{lstlisting} int d[N]; \end{lstlisting} where $\texttt{d}[x]$ will contain -the value $f(x)$. -The constant $N$ should be chosen so +the value of $f(x)$. +The constant $N$ has to be chosen so that all required values of the function fit in the array. @@ -208,23 +207,23 @@ The function handles the base cases $x=0$ and $x<0$ as previously. Then the function checks if $f(x)$ has already been calculated -and stored in $\texttt{d}[x]$. -If $f(x)$ is found in the array, +in $\texttt{d}[x]$. +If the value of $f(x)$ is found in the array, the function directly returns it. Otherwise the function calculates the value recursively and stores it in $\texttt{d}[x]$. Using memoization the function works -efficiently, because the answer for each $x$ +efficiently, because the answer for each parameter $x$ is calculated recursively only once. After a value of $f(x)$ has been stored in the array, it can be efficiently retrieved whenever the -function will be called again with parameter $x$. +function will be called again with the parameter $x$. The time complexity of the resulting algorithm is $O(xk)$ where the sum is $x$ and the number of coins is $k$. -In practice, the algorithm is usable if +In practice, the algorithm can be used if $x$ is so small that it is possible to allocate an array for all possible function parameters. @@ -294,7 +293,7 @@ while (x > 0) { \subsubsection{Counting the number of solutions} -Let us now consider a variation of the problem +Let us now consider a variant of the problem that is otherwise like the original problem, but we should count the total number of solutions instead of finding the optimal solution. @@ -319,7 +318,7 @@ The difference is that when finding the optimal solution, we maximize or minimize something in the recursion, but now we will calculate sums of numbers of solutions. -In the coin problem, we can define a function $f(x)$ +To solve the problem, we can define a function $f(x)$ that returns the number of ways to construct the sum $x$ using the coins. For example, $f(5)=6$ when the coins are $\{1,3,4\}$. @@ -330,7 +329,7 @@ because to form the sum $x$, we have to first choose some coin $c_i$ and then form the sum $x-c_i$. 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's not possible +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 @@ -370,8 +369,8 @@ 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 -all calculations are done in modulo $m$. -In the above code, it is enough to add the line +all calculations are done modulo $m$. +In the above code, it suffices to add the line \begin{lstlisting} d[i] %= m; \end{lstlisting} @@ -380,7 +379,7 @@ after the line d[i] += d[i-c[j]]; \end{lstlisting} -Now we have covered all basic +Now we have discussed all basic techniques related to dynamic programming. Since dynamic programming can be used @@ -397,7 +396,7 @@ Given an array that contains $n$ numbers $x_1,x_2,\ldots,x_n$, our task is to find the \key{longest increasing subsequence} -in the array. +of the array. This is a sequence of array elements that goes from left to right, and each element in the sequence is larger @@ -496,8 +495,8 @@ where $i