Corrections

This commit is contained in:
Antti H S Laaksonen 2017-02-13 21:42:16 +02:00
parent faa9ca2518
commit 3dd874a4fa
4 changed files with 179 additions and 179 deletions

View File

@ -6,7 +6,7 @@ Competitive programming combines two topics:
The \key{design of algorithms} consists of problem solving The \key{design of algorithms} consists of problem solving
and mathematical thinking. and mathematical thinking.
Skills for analyzing problems and solving them Skills for analyzing problems and solving them
creatively is needed. creatively are needed.
An algorithm for solving a problem An algorithm for solving a problem
has to be both correct and efficient, has to be both correct and efficient,
and the core of the problem is often and the core of the problem is often
@ -28,14 +28,14 @@ are graded by testing an implemented algorithm
using a set of test cases. using a set of test cases.
Thus, it is not enough that the idea of the Thus, it is not enough that the idea of the
algorithm is correct, but the implementation has algorithm is correct, but the implementation has
to be correct as well. also to be correct.
Good coding style in contests is Good coding style in contests is
straightforward and concise. straightforward and concise.
The solutions should be written quickly, The programs should be written quickly,
because there is not much time available. because there is not much time available.
Unlike in traditional software engineering, Unlike in traditional software engineering,
the solutions are short (usually at most some the programs are short (usually at most some
hundreds of lines) and it is not needed to hundreds of lines) and it is not needed to
maintain them after the contest. maintain them after the contest.
@ -49,7 +49,7 @@ For example, in Google Code Jam 2016,
among the best 3,000 participants, among the best 3,000 participants,
73 \% used C++, 73 \% used C++,
15 \% used Python and 15 \% used Python and
10 \% used Java\footnote{\url{https://www.go-hero.net/jam/16}}. 10 \% used Java. %\footnote{\url{https://www.go-hero.net/jam/16}}
Some participants also used several languages. Some participants also used several languages.
Many people think that C++ is the best choice Many people think that C++ is the best choice
@ -65,11 +65,11 @@ of data structures and algorithms.
On the other hand, it is good to On the other hand, it is good to
master several languages and know master several languages and know
their strengths. their strengths.
For example, if large numbers are needed For example, if large integers are needed
in the problem, in the problem,
Python can be a good choice because it Python can be a good choice, because it
contains a built-in library for handling contains built-in operations for
large numbers. calculating with large integers.
Still, most problems in programming contests Still, most problems in programming contests
are set so that are set so that
using a specific programming language using a specific programming language
@ -99,8 +99,8 @@ int main() {
\end{lstlisting} \end{lstlisting}
The \texttt{\#include} line at the beginning The \texttt{\#include} line at the beginning
of the code is a feature in the \texttt{g++} compiler of the code is a feature of the \texttt{g++} compiler
that allows us to include the whole standard library. that allows us to include the entire standard library.
Thus, it is not needed to separately include Thus, it is not needed to separately include
libraries such as \texttt{iostream}, libraries such as \texttt{iostream},
\texttt{vector} and \texttt{algorithm}, \texttt{vector} and \texttt{algorithm},
@ -112,7 +112,7 @@ of the standard library can be used directly
in the code. in the code.
Without the \texttt{using} line we should write, Without the \texttt{using} line we should write,
for example, \texttt{std::cout}, for example, \texttt{std::cout},
but now it is enough to write \texttt{cout}. but now it suffices to write \texttt{cout}.
The code can be compiled using the following command: The code can be compiled using the following command:
@ -122,7 +122,7 @@ g++ -std=c++11 -O2 -Wall code.cpp -o code
This command produces a binary file \texttt{code} This command produces a binary file \texttt{code}
from the source code \texttt{code.cpp}. from the source code \texttt{code.cpp}.
The compiler obeys the C++11 standard The compiler follows the C++11 standard
(\texttt{-std=c++11}), (\texttt{-std=c++11}),
optimizes the code (\texttt{-O2}) optimizes the code (\texttt{-O2})
and shows warnings about possible errors (\texttt{-Wall}). and shows warnings about possible errors (\texttt{-Wall}).
@ -152,8 +152,8 @@ cin >> a >> b >> x;
This kind of code always works, This kind of code always works,
assuming that there is at least one space assuming that there is at least one space
or one newline between each element in the input. or newline between each element in the input.
For example, the above code accepts For example, the above code can read
both the following inputs: both the following inputs:
\begin{lstlisting} \begin{lstlisting}
123 456 monkey 123 456 monkey
@ -203,7 +203,7 @@ printf("%d %d\n", a, b);
Sometimes the program should read a whole line Sometimes the program should read a whole line
from the input, possibly with spaces. from the input, possibly with spaces.
This can be accomplished using the This can be accomplished by using the
\texttt{getline} function: \texttt{getline} function:
\begin{lstlisting} \begin{lstlisting}
@ -212,7 +212,7 @@ getline(cin, s);
\end{lstlisting} \end{lstlisting}
If the amount of data is unknown, the following If the amount of data is unknown, the following
loop can be handy: loop is useful:
\begin{lstlisting} \begin{lstlisting}
while (cin >> x) { while (cin >> x) {
// code // code
@ -231,11 +231,11 @@ but add the following lines to the beginning of the code:
freopen("input.txt", "r", stdin); freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout); freopen("output.txt", "w", stdout);
\end{lstlisting} \end{lstlisting}
After this, the code reads the input from the file After this, the program reads the input from the file
''input.txt'' and writes the output to the file ''input.txt'' and writes the output to the file
''output.txt''. ''output.txt''.
\section{Handling numbers} \section{Working with numbers}
\index{integer} \index{integer}
@ -299,19 +299,19 @@ when $x$ is divided by $m$.
For example, $17 \bmod 5 = 2$, For example, $17 \bmod 5 = 2$,
because $17 = 3 \cdot 5 + 2$. because $17 = 3 \cdot 5 + 2$.
Sometimes, the answer for a problem is a Sometimes, the answer to a problem is a
very large number but it is enough to very large number but it is enough to
output it ''modulo $m$'', i.e., output it ''modulo $m$'', i.e.,
the remainder when the answer is divided by $m$ the remainder when the answer is divided by $m$
(for example, ''modulo $10^9+7$''). (for example, ''modulo $10^9+7$'').
The idea is that even if the actual answer The idea is that even if the actual answer
may be very large, may be very large,
it is enough to use the types it suffices to use the types
\texttt{int} and \texttt{long long}. \texttt{int} and \texttt{long long}.
An important property of the remainder is that An important property of the remainder is that
in addition, subtraction and multiplication, in addition, subtraction and multiplication,
the remainder can be calculated before the operation: the remainder can be taken before the operation:
\[ \[
\begin{array}{rcr} \begin{array}{rcr}
@ -321,7 +321,7 @@ the remainder can be calculated before the operation:
\end{array} \end{array}
\] \]
Thus, we can calculate the remainder after every operation Thus, we can take the remainder after every operation
and the numbers will never become too large. and the numbers will never become too large.
For example, the following code calculates $n!$, For example, the following code calculates $n!$,
@ -339,8 +339,8 @@ be between $0\ldots m-1$.
However, in C++ and other languages, However, in C++ and other languages,
the remainder of a negative number the remainder of a negative number
is either zero or negative. is either zero or negative.
An easy way to make sure this will An easy way to make sure there
not happen is to first calculate are no negative remainders is to first calculate
the remainder as usual and then add $m$ the remainder as usual and then add $m$
if the result is negative: if the result is negative:
\begin{lstlisting} \begin{lstlisting}
@ -378,7 +378,8 @@ printf("%.9f\n", x);
A difficulty when using floating point numbers A difficulty when using floating point numbers
is that some numbers cannot be represented is that some numbers cannot be represented
accurately but there will be rounding errors. accurately as floating point numbers,
but there will be rounding errors.
For example, the result of the following code For example, the result of the following code
is surprising: is surprising:
@ -387,14 +388,14 @@ double x = 0.3*3+0.1;
printf("%.20f\n", x); // 0.99999999999999988898 printf("%.20f\n", x); // 0.99999999999999988898
\end{lstlisting} \end{lstlisting}
Because of a rounding error, Due to a rounding error,
the value of \texttt{x} is a bit less than 1, the value of \texttt{x} is a bit smaller than 1,
while the correct value would be 1. while the correct value would be 1.
It is risky to compare floating point numbers It is risky to compare floating point numbers
with the \texttt{==} operator, with the \texttt{==} operator,
because it is possible that the values should because it is possible that the values should
be equal but they are not due to rounding errors. be equal but they are not because of rounding.
A better way to compare floating point numbers A better way to compare floating point numbers
is to assume that two numbers are equal is to assume that two numbers are equal
if the difference between them is $\varepsilon$, if the difference between them is $\varepsilon$,
@ -414,12 +415,12 @@ integers up to a certain limit can be still
represented accurately. represented accurately.
For example, using \texttt{double}, For example, using \texttt{double},
it is possible to accurately represent all it is possible to accurately represent all
integers having absolute value at most $2^{53}$. integers whose absolute value is at most $2^{53}$.
\section{Shortening code} \section{Shortening code}
Short code is ideal in competitive programming, Short code is ideal in competitive programming,
because algorithms should be implemented because programs should be written
as fast as possible. as fast as possible.
Because of this, competitive programmers often define Because of this, competitive programmers often define
shorter names for datatypes and other parts of code. shorter names for datatypes and other parts of code.
@ -450,7 +451,7 @@ cout << a*b << "\n";
The command \texttt{typedef} The command \texttt{typedef}
can also be used with more complex types. can also be used with more complex types.
For example, the following code gives For example, the following code gives
the name \texttt{vi} for a vector of integers, the name \texttt{vi} for a vector of integers
and the name \texttt{pi} for a pair and the name \texttt{pi} for a pair
that contains two integers. that contains two integers.
\begin{lstlisting} \begin{lstlisting}
@ -511,16 +512,16 @@ REP(i,1,n) {
Mathematics plays an important role in competitive Mathematics plays an important role in competitive
programming, and it is not possible to become programming, and it is not possible to become
a successful competitive programmer without good skills a successful competitive programmer without
in mathematics. having good mathematical skills.
This section covers some important This section discusses some important
mathematical concepts and formulas that mathematical concepts and formulas that
are needed later in the book. are needed later in the book.
\subsubsection{Sum formulas} \subsubsection{Sum formulas}
Each sum of the form Each sum of the form
\[\sum_{x=1}^n x^k = 1^k+2^k+3^k+\ldots+n^k\] \[\sum_{x=1}^n x^k = 1^k+2^k+3^k+\ldots+n^k,\]
where $k$ is a positive integer, where $k$ is a positive integer,
has a closed-form formula that is a has a closed-form formula that is a
polynomial of degree $k+1$. polynomial of degree $k+1$.
@ -529,13 +530,14 @@ For example,
and and
\[\sum_{x=1}^n x^2 = 1^2+2^2+3^2+\ldots+n^2 = \frac{n(n+1)(2n+1)}{6}.\] \[\sum_{x=1}^n x^2 = 1^2+2^2+3^2+\ldots+n^2 = \frac{n(n+1)(2n+1)}{6}.\]
An \key{arithmetic sum} is a sum \index{arithmetic sum} An \key{arithmetic progression} is a \index{arithmetic progression}
sequence of numbers
where the difference between any two consecutive where the difference between any two consecutive
numbers is constant. numbers is constant.
For example, For example,
\[3+7+11+15\] \[3, 7, 11, 15\]
is an arithmetic sum with constant 4. is an arithmetic progression with constant 4.
An arithmetic sum can be calculated The sum of an arithmetic progression can be calculated
using the formula using the formula
\[\frac{n(a+b)}{2}\] \[\frac{n(a+b)}{2}\]
where $a$ is the first number, where $a$ is the first number,
@ -547,14 +549,15 @@ The formula is based on the fact
that the sum consists of $n$ numbers and that the sum consists of $n$ numbers and
the value of each number is $(a+b)/2$ on average. the value of each number is $(a+b)/2$ on average.
\index{geometric sum} \index{geometric progression}
A \key{geometric sum} is a sum A \key{geometric progression} is a sequence
of numbers
where the ratio between any two consecutive where the ratio between any two consecutive
numbers is constant. numbers is constant.
For example, For example,
\[3+6+12+24\] \[3,6,12,24\]
is a geometric sum with constant 2. is a geometric progression with constant 2.
A geometric sum can be calculated The sum of a geometric progression can be calculated
using the formula using the formula
\[\frac{bx-a}{x-1}\] \[\frac{bx-a}{x-1}\]
where $a$ is the first number, where $a$ is the first number,
@ -571,7 +574,7 @@ and solving the equation
\[ xS-S = bx-a\] \[ xS-S = bx-a\]
yields the formula. yields the formula.
A special case of a geometric sum is the formula A special case of a sum of a geometric progression is the formula
\[1+2+4+8+\ldots+2^{n-1}=2^n-1.\] \[1+2+4+8+\ldots+2^{n-1}=2^n-1.\]
\index{harmonic sum} \index{harmonic sum}
@ -579,7 +582,7 @@ A special case of a geometric sum is the formula
A \key{harmonic sum} is a sum of the form A \key{harmonic sum} is a sum of the form
\[ \sum_{x=1}^n \frac{1}{x} = 1+\frac{1}{2}+\frac{1}{3}+\ldots+\frac{1}{n}.\] \[ \sum_{x=1}^n \frac{1}{x} = 1+\frac{1}{2}+\frac{1}{3}+\ldots+\frac{1}{n}.\]
An upper bound for the harmonic sum is $\log_2(n)+1$. An upper bound for a harmonic sum is $\log_2(n)+1$.
Namely, we can Namely, we can
modify each term $1/k$ so that $k$ becomes modify each term $1/k$ so that $k$ becomes
the nearest power of two that does not exceed $k$. the nearest power of two that does not exceed $k$.
@ -589,7 +592,7 @@ the sum as follows:
1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}.\] 1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}.\]
This upper bound consists of $\log_2(n)+1$ parts This upper bound consists of $\log_2(n)+1$ parts
($1$, $2 \cdot 1/2$, $4 \cdot 1/4$, etc.), ($1$, $2 \cdot 1/2$, $4 \cdot 1/4$, etc.),
and the sum of each part is at most 1. and the value of each part is at most 1.
\subsubsection{Set theory} \subsubsection{Set theory}
@ -607,7 +610,7 @@ For example, the set
\[X=\{2,4,7\}\] \[X=\{2,4,7\}\]
contains elements 2, 4 and 7. contains elements 2, 4 and 7.
The symbol $\emptyset$ denotes an empty set, The symbol $\emptyset$ denotes an empty set,
and $|S|$ denotes the size of the set $S$, and $|S|$ denotes the size of a set $S$,
i.e., the number of elements in the set. i.e., the number of elements in the set.
For example, in the above set, $|X|=3$. For example, in the above set, $|X|=3$.
@ -654,15 +657,11 @@ $\{2\}$, $\{4\}$, $\{7\}$, $\{2,4\}$, $\{2,7\}$, $\{4,7\}$ and $\{2,4,7\}$.
\end{center} \end{center}
Often used sets are Often used sets are
$\mathbb{N}$ (natural numbers),
\begin{itemize}[noitemsep] $\mathbb{Z}$ (integers),
\item $\mathbb{N}$ (natural numbers), $\mathbb{Q}$ (rational numbers) and
\item $\mathbb{Z}$ (integers), $\mathbb{R}$ (real numbers).
\item $\mathbb{Q}$ (rational numbers) and The set $\mathbb{N}$
\item $\mathbb{R}$ (real numbers).
\end{itemize}
The set $\mathbb{N}$ of natural numbers
can be defined in two ways, depending can be defined in two ways, depending
on the situation: on the situation:
either $\mathbb{N}=\{0,1,2,\ldots\}$ either $\mathbb{N}=\{0,1,2,\ldots\}$
@ -707,7 +706,7 @@ $A$ & $B$ & $\lnot A$ & $\lnot B$ & $A \land B$ & $A \lor B$ & $A \Rightarrow B$
\end{tabular} \end{tabular}
\end{center} \end{center}
The expression $\lnot A$ has the reverse value of $A$. The expression $\lnot A$ has the opposite value of $A$.
The expression $A \land B$ is true if both $A$ and $B$ The expression $A \land B$ is true if both $A$ and $B$
are true, are true,
and the expression $A \lor B$ is true if $A$ or $B$ or both and the expression $A \lor B$ is true if $A$ or $B$ or both
@ -729,7 +728,7 @@ Using this definition, $P(7)$ is true but $P(8)$ is false.
\index{quantifier} \index{quantifier}
A \key{quantifier} connects a logical expression A \key{quantifier} connects a logical expression
to elements in a set. to the elements of a set.
The most important quantifiers are The most important quantifiers are
$\forall$ (\key{for all}) and $\exists$ (\key{there is}). $\forall$ (\key{for all}) and $\exists$ (\key{there is}).
For example, For example,
@ -746,7 +745,7 @@ For example,
\[\forall x ((x>1 \land \lnot P(x)) \Rightarrow (\exists a (\exists b (x = ab \land a > 1 \land b > 1))))\] \[\forall x ((x>1 \land \lnot P(x)) \Rightarrow (\exists a (\exists b (x = ab \land a > 1 \land b > 1))))\]
means that if a number $x$ is larger than 1 means that if a number $x$ is larger than 1
and not a prime number, and not a prime number,
then there exist numbers $a$ and $b$ then there are numbers $a$ and $b$
that are larger than $1$ and whose product is $x$. that are larger than $1$ and whose product is $x$.
This proposition is true in the set of integers. This proposition is true in the set of integers.
@ -804,7 +803,7 @@ of the logarithm.
According to the definition, According to the definition,
$\log_k(x)=a$ exactly when $k^a=x$. $\log_k(x)=a$ exactly when $k^a=x$.
A useful interpretation in algorithm design is A useful property of logarithms is
that $\log_k(x)$ equals the number of times that $\log_k(x)$ equals the number of times
we have to divide $x$ by $k$ before we reach we have to divide $x$ by $k$ before we reach
the number 1. the number 1.
@ -813,9 +812,9 @@ because 5 divisions are needed:
\[32 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 \] \[32 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 \]
Logarithms are often needed in the analysis of Logarithms are often used in the analysis of
algorithms, because many efficient algorithms algorithms, because many efficient algorithms
divide in half something at each step. halve something at each step.
Hence, we can estimate the efficiency of such algorithms Hence, we can estimate the efficiency of such algorithms
using logarithms. using logarithms.
@ -837,9 +836,9 @@ The \key{natural logarithm} $\ln(x)$ of a number $x$
is a logarithm whose base is $e \approx 2{,}71828$. is a logarithm whose base is $e \approx 2{,}71828$.
Another property of logarithms is that Another property of logarithms is that
the number of digits of a number $x$ in base $b$ is the number of digits of an integer $x$ in base $b$ is
$\lfloor \log_b(x)+1 \rfloor$. $\lfloor \log_b(x)+1 \rfloor$.
For example, the representation of For example, the representation of
the number $123$ in base $2$ is 1111011 and $123$ in base $2$ is 1111011 and
$\lfloor \log_2(123)+1 \rfloor = 7$. $\lfloor \log_2(123)+1 \rfloor = 7$.

View File

@ -59,7 +59,7 @@ for (int i = 1; i <= n; i++) {
\subsubsection*{Order of magnitude} \subsubsection*{Order of magnitude}
A time complexity does not indicate the exact number A time complexity does not tell us the exact number
of times the code inside a loop is executed, of times the code inside a loop is executed,
but it only shows the order of magnitude. but it only shows the order of magnitude.
In the following examples, the code inside the loop In the following examples, the code inside the loop
@ -210,7 +210,7 @@ $n$ must be divided by 2 to get 1.
\item[$O(\sqrt n)$] \item[$O(\sqrt n)$]
A \key{square root algorithm} is slower than A \key{square root algorithm} is slower than
$O(\log n)$ but faster than $O(n)$. $O(\log n)$ but faster than $O(n)$.
A special feature of square roots is that A special property of square roots is that
$\sqrt n = n/\sqrt n$, so the square root $\sqrt n$ lies $\sqrt n = n/\sqrt n$, so the square root $\sqrt n$ lies
in some sense in the middle of the input. in some sense in the middle of the input.
@ -225,7 +225,7 @@ reporting the answer.
\item[$O(n \log n)$] \item[$O(n \log n)$]
This time complexity often indicates that the This time complexity often indicates that the
algorithm sorts the input algorithm sorts the input,
because the time complexity of efficient because the time complexity of efficient
sorting algorithms is $O(n \log n)$. sorting algorithms is $O(n \log n)$.
Another possibility is that the algorithm Another possibility is that the algorithm
@ -237,14 +237,14 @@ takes $O(\log n)$ time.
A \key{quadratic} algorithm often contains A \key{quadratic} algorithm often contains
two nested loops. two nested loops.
It is possible to go through all pairs of It is possible to go through all pairs of
input elements in $O(n^2)$ time. the input elements in $O(n^2)$ time.
\item[$O(n^3)$] \item[$O(n^3)$]
\index{cubic algorithm} \index{cubic algorithm}
A \key{cubic} algorithm often contains A \key{cubic} algorithm often contains
three nested loops. three nested loops.
It is possible to go through all triplets of It is possible to go through all triplets of
input elements in $O(n^3)$ time. the input elements in $O(n^3)$ time.
\item[$O(2^n)$] \item[$O(2^n)$]
This time complexity often indicates that This time complexity often indicates that
@ -285,9 +285,9 @@ of problems for which no polynomial algorithm is known.
\section{Estimating efficiency} \section{Estimating efficiency}
By calculating the time complexity, By calculating the time complexity of an algorithm,
it is possible to check before it is possible to check before
implementing an algorithm that it is implementing the algorithm that it is
efficient enough for the problem. efficient enough for the problem.
The starting point for estimations is the fact that The starting point for estimations is the fact that
a modern computer can perform some hundreds of a modern computer can perform some hundreds of
@ -297,7 +297,7 @@ For example, assume that the time limit for
a problem is one second and the input size is $n=10^5$. a problem is one second and the input size is $n=10^5$.
If the time complexity is $O(n^2)$, If the time complexity is $O(n^2)$,
the algorithm will perform about $(10^5)^2=10^{10}$ operations. the algorithm will perform about $(10^5)^2=10^{10}$ operations.
This should take at least some tens of seconds time, This should take at least some tens of seconds,
so the algorithm seems to be too slow for solving the problem. so the algorithm seems to be too slow for solving the problem.
On the other hand, given the input size, On the other hand, given the input size,
@ -326,7 +326,7 @@ it is probably expected that the time
complexity of the algorithm is $O(n)$ or $O(n \log n)$. complexity of the algorithm is $O(n)$ or $O(n \log n)$.
This information makes it easier to design the algorithm, This information makes it easier to design the algorithm,
because it rules out approaches that would yield because it rules out approaches that would yield
an algorithm with a slower time complexity. an algorithm with a worse time complexity.
\index{constant factor} \index{constant factor}
@ -412,9 +412,9 @@ the following subarray produces the maximum sum $10$:
\end{center} \end{center}
\end{samepage} \end{samepage}
\subsubsection{Solution 1} \subsubsection{Algorithm 1}
A straightforward solution to the problem A straightforward algorithm to the problem
is to go through all possible ways to is to go through all possible ways to
select a subarray, calculate the sum of select a subarray, calculate the sum of
numbers in each subarray and maintain numbers in each subarray and maintain
@ -437,18 +437,18 @@ cout << p << "\n";
The code assumes that the numbers are stored in an array \texttt{x} The code assumes that the numbers are stored in an array \texttt{x}
with indices $1 \ldots n$. with indices $1 \ldots n$.
The variables $a$ and $b$ select the first and last The variables $a$ and $b$ determine the first and last
number in the subarray, number in the subarray,
and the sum of the subarray is calculated to the variable $s$. and the sum of the numbers is calculated to the variable $s$.
The variable $p$ contains the maximum sum found during the search. The variable $p$ contains the maximum sum found during the search.
The time complexity of the algorithm is $O(n^3)$, The time complexity of the algorithm is $O(n^3)$,
because it consists of three nested loops and because it consists of three nested loops and
each loop contains $O(n)$ steps. each loop contains $O(n)$ steps.
\subsubsection{Solution 2} \subsubsection{Algorithm 2}
It is easy to make the first solution more efficient It is easy to make the first algorithm more efficient
by removing one loop from it. by removing one loop from it.
This is possible by calculating the sum at the same This is possible by calculating the sum at the same
time when the right end of the subarray moves. time when the right end of the subarray moves.
@ -467,17 +467,17 @@ cout << p << "\n";
\end{lstlisting} \end{lstlisting}
After this change, the time complexity is $O(n^2)$. After this change, the time complexity is $O(n^2)$.
\subsubsection{Solution 3} \subsubsection{Algorithm 3}
Surprisingly, it is possible to solve the problem Surprisingly, it is possible to solve the problem
in $O(n)$ time, which means that we can remove in $O(n)$ time, which means that we can remove
one more loop. one more loop.
The idea is to calculate for each array position The idea is to calculate for each array position
the maximum subarray sum that ends at that position. the maximum sum of a subarray that ends at that position.
After this, the answer for the problem is the After this, the answer for the problem is the
maximum of those sums. maximum of those sums.
Condider the subproblem of finding the maximum subarray Condider the subproblem of finding the maximum-sum subarray
that ends at position $k$. that ends at position $k$.
There are two possibilities: There are two possibilities:
\begin{enumerate} \begin{enumerate}
@ -487,13 +487,13 @@ at position $k-1$, followed by the element at position $k$.
\end{enumerate} \end{enumerate}
Our goal is to find a subarray with maximum sum, Our goal is to find a subarray with maximum sum,
so in case 2 the subarray that ends at index $k-1$ so in case 2 the subarray that ends at position $k-1$
should also have the maximum sum. should also have the maximum sum.
Thus, we can solve the problem efficiently Thus, we can solve the problem efficiently
when we calculate the maximum subarray sum when we calculate the maximum subarray sum
for each ending position from left to right. for each ending position from left to right.
The following code implements the solution: The following code implements the algorithm:
\begin{lstlisting} \begin{lstlisting}
int p = 0, s = 0; int p = 0, s = 0;
for (int k = 1; k <= n; k++) { for (int k = 1; k <= n; k++) {
@ -508,7 +508,7 @@ that goes through the input,
so the time complexity is $O(n)$. so the time complexity is $O(n)$.
This is also the best possible time complexity, This is also the best possible time complexity,
because any algorithm for the problem because any algorithm for the problem
has to access all array elements at least once. has to examine all array elements at least once.
\subsubsection{Efficiency comparison} \subsubsection{Efficiency comparison}
@ -524,7 +524,7 @@ measured.
\begin{center} \begin{center}
\begin{tabular}{rrrr} \begin{tabular}{rrrr}
array size $n$ & solution 1 & solution 2 & solution 3 \\ array size $n$ & algorithm 1 & algorithm 2 & algorithm 3 \\
\hline \hline
$10^2$ & $0{,}0$ s & $0{,}0$ s & $0{,}0$ s \\ $10^2$ & $0{,}0$ s & $0{,}0$ s & $0{,}0$ s \\
$10^3$ & $0{,}1$ s & $0{,}0$ s & $0{,}0$ s \\ $10^3$ & $0{,}1$ s & $0{,}0$ s & $0{,}0$ s \\
@ -539,8 +539,8 @@ The comparison shows that all algorithms
are efficient when the input size is small, are efficient when the input size is small,
but larger inputs bring out remarkable but larger inputs bring out remarkable
differences in running times of the algorithms. differences in running times of the algorithms.
The $O(n^3)$ time solution 1 becomes slow The $O(n^3)$ time algorithm 1 becomes slow
when $n=10^4$, and the $O(n^2)$ time solution 2 when $n=10^4$, and the $O(n^2)$ time algorithm 2
becomes slow when $n=10^5$. becomes slow when $n=10^5$.
Only the $O(n)$ time solution 3 solves Only the $O(n)$ time algorithm 3 processes
even the largest inputs instantly. even the largest inputs instantly.

View File

@ -4,22 +4,22 @@
\key{Sorting} \key{Sorting}
is a fundamental algorithm design problem. is a fundamental algorithm design problem.
In addition, Many efficient algorithms
many efficient algorithms
use sorting as a subroutine, use sorting as a subroutine,
because it is often easier to process because it is often easier to process
data if the elements are in a sorted order. data if the elements are in a sorted order.
For example, the question ''does the array contain For example, the problem ''does the array contain
two equal elements?'' is easy to solve using sorting. two equal elements?'' is easy to solve using sorting.
If the array contains two equal elements, If the array contains two equal elements,
they will be next to each other after sorting, they will be next to each other after sorting,
so it is easy to find them. so it is easy to find them.
Also the question ''what is the most frequent element Also the problem ''what is the most frequent element
in the array?'' can be solved similarly. in the array?'' can be solved similarly.
There are many algorithms for sorting, that are There are many algorithms for sorting, and they are
also good examples of algorithm design techniques. also good examples of how to apply
different algorithm design techniques.
The efficient general sorting algorithms The efficient general sorting algorithms
work in $O(n \log n)$ time, work in $O(n \log n)$ time,
and many algorithms that use sorting and many algorithms that use sorting
@ -99,15 +99,15 @@ is \key{bubble sort} where the elements
Bubble sort consists of $n-1$ rounds. Bubble sort consists of $n-1$ rounds.
On each round, the algorithm iterates through On each round, the algorithm iterates through
the elements in the array. the elements of the array.
Whenever two consecutive elements are found Whenever two consecutive elements are found
that are not in correct order, that are not in correct order,
the algorithm swaps them. the algorithm swaps them.
The algorithm can be implemented as follows The algorithm can be implemented as follows
for array for an array
$\texttt{t}[1],\texttt{t}[2],\ldots,\texttt{t}[n]$: $\texttt{t}[1],\texttt{t}[2],\ldots,\texttt{t}[n]$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n-1; i++) {
for (int j = 1; j <= n-i; j++) { for (int j = 1; j <= n-i; j++) {
if (t[j] > t[j+1]) swap(t[j],t[j+1]); if (t[j] > t[j+1]) swap(t[j],t[j+1]);
} }
@ -118,7 +118,7 @@ After the first round of the algorithm,
the largest element will be in the correct position, the largest element will be in the correct position,
and in general, after $k$ rounds, the $k$ largest and in general, after $k$ rounds, the $k$ largest
elements will be in the correct positions. elements will be in the correct positions.
Thus, after $n$ rounds, the whole array Thus, after $n-1$ rounds, the whole array
will be sorted. will be sorted.
For example, in the array For example, in the array
@ -267,7 +267,7 @@ algorithm that always swaps consecutive
elements in the array. elements in the array.
It turns out that the time complexity It turns out that the time complexity
of such an algorithm is \emph{always} of such an algorithm is \emph{always}
at least $O(n^2)$ because in the worst case, at least $O(n^2)$, because in the worst case,
$O(n^2)$ swaps are required for sorting the array. $O(n^2)$ swaps are required for sorting the array.
A useful concept when analyzing sorting A useful concept when analyzing sorting
@ -302,7 +302,7 @@ For example, in the array
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
the inversions are $(6,3)$, $(6,5)$ and $(9,8)$. the inversions are $(6,3)$, $(6,5)$ and $(9,8)$.
The number of inversions indicates The number of inversions tells us
how much work is needed to sort the array. how much work is needed to sort the array.
An array is completely sorted when An array is completely sorted when
there are no inversions. there are no inversions.
@ -332,20 +332,20 @@ that is based on recursion.
Mergesort sorts a subarray \texttt{t}$[a,b]$ as follows: Mergesort sorts a subarray \texttt{t}$[a,b]$ as follows:
\begin{enumerate} \begin{enumerate}
\item If $a=b$, do not do anything because the subarray is already sorted. \item If $a=b$, do not do anything, because the subarray is already sorted.
\item Calculate the index of the middle element: $k=\lfloor (a+b)/2 \rfloor$. \item Calculate the position of the middle element: $k=\lfloor (a+b)/2 \rfloor$.
\item Recursively sort the subarray \texttt{t}$[a,k]$. \item Recursively sort the subarray \texttt{t}$[a,k]$.
\item Recursively sort the subarray \texttt{t}$[k+1,b]$. \item Recursively sort the subarray \texttt{t}$[k+1,b]$.
\item \emph{Merge} the sorted subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$ \item \emph{Merge} the sorted subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
into a sorted subarray \texttt{t}$[a,b]$. into a sorted subarray \texttt{t}$[a,b]$.
\end{enumerate} \end{enumerate}
Mergesort is an efficient algorithm because it Mergesort is an efficient algorithm, because it
halves the size of the subarray at each step. halves the size of the subarray at each step.
The recursion consists of $O(\log n)$ levels, The recursion consists of $O(\log n)$ levels,
and processing each level takes $O(n)$ time. and processing each level takes $O(n)$ time.
Merging the subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$ Merging the subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
is possible in linear time because they are already sorted. is possible in linear time, because they are already sorted.
For example, consider sorting the following array: For example, consider sorting the following array:
\begin{center} \begin{center}
@ -466,7 +466,7 @@ when we restrict ourselves to sorting algorithms
that are based on comparing array elements. that are based on comparing array elements.
The lower bound for the time complexity The lower bound for the time complexity
can be proved by examining the sorting can be proved by considering sorting
as a process where each comparison of two elements as a process where each comparison of two elements
gives more information about the contents of the array. gives more information about the contents of the array.
The process creates the following tree: The process creates the following tree:
@ -599,10 +599,10 @@ corresponds to the following bookkeeping array:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
For example, the value at index 3 For example, the value at position 3
in the bookkeeping array is 2, in the bookkeeping array is 2,
because the element 3 appears 2 times because the element 3 appears 2 times
in the original array (indices 2 and 6). in the original array (positions 2 and 6).
The construction of the bookkeeping array The construction of the bookkeeping array
takes $O(n)$ time. After this, the sorted array takes $O(n)$ time. After this, the sorted array
@ -621,8 +621,8 @@ be used as indices in the bookkeeping array.
\index{sort@\texttt{sort}} \index{sort@\texttt{sort}}
It is almost never a good idea to implement It is almost never a good idea to use
an own sorting algorithm a self-made sorting algorithm
in a contest, because there are good in a contest, because there are good
implementations available in programming languages. implementations available in programming languages.
For example, the C++ standard library contains For example, the C++ standard library contains
@ -634,7 +634,7 @@ First, it saves time because there is no need to
implement the function. implement the function.
In addition, the library implementation is In addition, the library implementation is
certainly correct and efficient: it is not probable certainly correct and efficient: it is not probable
that a home-made sorting function would be better. that a self-made sorting function would be better.
In this section we will see how to use the In this section we will see how to use the
C++ \texttt{sort} function. C++ \texttt{sort} function.
@ -652,7 +652,7 @@ but a reverse order is possible as follows:
\begin{lstlisting} \begin{lstlisting}
sort(v.rbegin(),v.rend()); sort(v.rbegin(),v.rend());
\end{lstlisting} \end{lstlisting}
A regular array can be sorted as follows: An ordinary array can be sorted as follows:
\begin{lstlisting} \begin{lstlisting}
int n = 7; // array size int n = 7; // array size
int t[] = {4,2,5,3,5,8,3}; int t[] = {4,2,5,3,5,8,3};
@ -667,7 +667,7 @@ Sorting a string means that the characters
in the string are sorted. in the string are sorted.
For example, the string ''monkey'' becomes ''ekmnoy''. For example, the string ''monkey'' becomes ''ekmnoy''.
\subsubsection{Comparison operator} \subsubsection{Comparison operators}
\index{comparison operator} \index{comparison operator}
@ -677,17 +677,17 @@ of the elements to be sorted.
During the sorting, this operator will be used During the sorting, this operator will be used
whenever it is needed to find out the order of two elements. whenever it is needed to find out the order of two elements.
Most C++ data types have a built-in comparison operator Most C++ data types have a built-in comparison operator,
and elements of those types can be sorted automatically. and elements of those types can be sorted automatically.
For example, numbers are sorted according to their values For example, numbers are sorted according to their values
and strings are sorted in alphabetical order. and strings are sorted in alphabetical order.
\index{pair@\texttt{pair}} \index{pair@\texttt{pair}}
Pairs (\texttt{pair}) are sorted primarily by the first Pairs (\texttt{pair}) are sorted primarily by their first
element (\texttt{first}). elements (\texttt{first}).
However, if the first elements of two pairs are equal, However, if the first elements of two pairs are equal,
they are sorted by the second element (\texttt{second}): they are sorted by their second elements (\texttt{second}):
\begin{lstlisting} \begin{lstlisting}
vector<pair<int,int>> v; vector<pair<int,int>> v;
v.push_back({1,5}); v.push_back({1,5});
@ -700,7 +700,7 @@ $(1,2)$, $(1,5)$ and $(2,3)$.
\index{tuple@\texttt{tuple}} \index{tuple@\texttt{tuple}}
Correspondingly, tuples (\texttt{tuple}) In a similar way, tuples (\texttt{tuple})
are sorted primarily by the first element, are sorted primarily by the first element,
secondarily by the second element, etc.: secondarily by the second element, etc.:
\begin{lstlisting} \begin{lstlisting}
@ -741,7 +741,7 @@ struct P {
}; };
\end{lstlisting} \end{lstlisting}
\subsubsection{Comparison function} \subsubsection{Comparison functions}
\index{comparison function} \index{comparison function}
@ -782,7 +782,7 @@ for (int i = 1; i <= n; i++) {
The time complexity of this approach is $O(n)$, The time complexity of this approach is $O(n)$,
because in the worst case, it is needed to check because in the worst case, it is needed to check
all elements in the array. all elements in the array.
If the array can contain any elements, If the array may contain any elements,
this is also the best possible approach, because this is also the best possible approach, because
there is no additional information available where there is no additional information available where
in the array we should search for the element $x$. in the array we should search for the element $x$.
@ -791,7 +791,7 @@ However, if the array is \emph{sorted},
the situation is different. the situation is different.
In this case it is possible to perform the In this case it is possible to perform the
search much faster, because the order of the search much faster, because the order of the
elements in the array guides us. elements in the array guides the search.
The following \key{binary search} algorithm The following \key{binary search} algorithm
efficiently searches for an element in a sorted array efficiently searches for an element in a sorted array
in $O(\log n)$ time. in $O(\log n)$ time.
@ -804,7 +804,7 @@ At each step, the search halves the active region in the array,
until the target element is found, or it turns out until the target element is found, or it turns out
that there is no such element. that there is no such element.
First, the search checks the middle element in the array. First, the search checks the middle element of the array.
If the middle element is the target element, If the middle element is the target element,
the search terminates. the search terminates.
Otherwise, the search recursively continues Otherwise, the search recursively continues
@ -823,7 +823,7 @@ while (a <= b) {
\end{lstlisting} \end{lstlisting}
The algorithm maintains a range $a \ldots b$ The algorithm maintains a range $a \ldots b$
that corresponds to the active region in the array. that corresponds to the active region of the array.
Initially, the range is $1 \ldots n$, the whole array. Initially, the range is $1 \ldots n$, the whole array.
The algorithm halves the size of the range at each step, The algorithm halves the size of the range at each step,
so the time complexity is $O(\log n)$. so the time complexity is $O(\log n)$.
@ -856,7 +856,7 @@ if (t[k] == x) // x was found at index k
The variables $k$ and $b$ contain the position The variables $k$ and $b$ contain the position
in the array and the jump length. in the array and the jump length.
If the array contains the element $x$, If the array contains the element $x$,
the index of the element will be in the variable $k$ the position of $x$ will be in the variable $k$
after the search. after the search.
The time complexity of the algorithm is $O(\log n)$, The time complexity of the algorithm is $O(\log n)$,
because the code in the \texttt{while} loop because the code in the \texttt{while} loop
@ -866,7 +866,7 @@ is performed at most twice for each jump length.
In practice, it is seldom needed to implement In practice, it is seldom needed to implement
binary search for searching elements in an array, binary search for searching elements in an array,
because we can use the standard library instead. because we can use the standard library.
For example, the C++ functions \texttt{lower\_bound} For example, the C++ functions \texttt{lower\_bound}
and \texttt{upper\_bound} implement binary search, and \texttt{upper\_bound} implement binary search,
and the data structure \texttt{set} maintains a and the data structure \texttt{set} maintains a
@ -893,7 +893,7 @@ $\texttt{ok}(x)$ & \texttt{false} & \texttt{false}
\end{center} \end{center}
\noindent \noindent
The value $k$ can be found using binary search: Now, the value $k$ can be found using binary search:
\begin{lstlisting} \begin{lstlisting}
int x = -1; int x = -1;
@ -947,7 +947,7 @@ for (int b = z; b >= 1; b /= 2) {
int k = x+1; int k = x+1;
\end{lstlisting} \end{lstlisting}
Note that unlike in the standard binary search, Note that unlike in the ordinary binary search,
here it is not allowed that consecutive values here it is not allowed that consecutive values
of the function are equal. of the function are equal.
In this case it would not be possible to know In this case it would not be possible to know

View File

@ -30,7 +30,7 @@ size can be changed during the execution
of the program. of the program.
The most popular dynamic array in C++ is The most popular dynamic array in C++ is
the \texttt{vector} structure, the \texttt{vector} structure,
that can be used almost like a regular array. that can be used almost like an ordinary array.
The following code creates an empty vector and The following code creates an empty vector and
adds three elements to it: adds three elements to it:
@ -42,7 +42,7 @@ v.push_back(2); // [3,2]
v.push_back(5); // [3,2,5] v.push_back(5); // [3,2,5]
\end{lstlisting} \end{lstlisting}
After this, the elements can be accessed like in a regular array: After this, the elements can be accessed like in an ordinary array:
\begin{lstlisting} \begin{lstlisting}
cout << v[0] << "\n"; // 3 cout << v[0] << "\n"; // 3
@ -102,7 +102,7 @@ vector<int> v(10, 5);
\end{lstlisting} \end{lstlisting}
The internal implementation of the vector The internal implementation of the vector
uses a regular array. uses an ordinary array.
If the size of the vector increases and If the size of the vector increases and
the array becomes too small, the array becomes too small,
a new array is allocated and all the a new array is allocated and all the
@ -119,7 +119,7 @@ In addition, there is special syntax for strings
that is not available in other data structures. that is not available in other data structures.
Strings can be combined using the \texttt{+} symbol. Strings can be combined using the \texttt{+} symbol.
The function $\texttt{substr}(k,x)$ returns the substring The function $\texttt{substr}(k,x)$ returns the substring
that begins at index $k$ and has length $x$, that begins at position $k$ and has length $x$,
and the function $\texttt{find}(\texttt{t})$ finds the position and the function $\texttt{find}(\texttt{t})$ finds the position
of the first occurrence of a substring \texttt{t}. of the first occurrence of a substring \texttt{t}.
@ -196,14 +196,14 @@ for (auto x : s) {
} }
\end{lstlisting} \end{lstlisting}
An important property of a set is An important property of sets
that all the elements are \emph{distinct}. that all the elements are \emph{distinct}.
Thus, the function \texttt{count} always returns Thus, the function \texttt{count} always returns
either 0 (the element is not in the set) either 0 (the element is not in the set)
or 1 (the element is in the set), or 1 (the element is in the set),
and the function \texttt{insert} never adds and the function \texttt{insert} never adds
an element to the set if it is an element to the set if it is
already in the set. already there.
The following code illustrates this: The following code illustrates this:
\begin{lstlisting} \begin{lstlisting}
@ -214,13 +214,13 @@ s.insert(5);
cout << s.count(5) << "\n"; // 1 cout << s.count(5) << "\n"; // 1
\end{lstlisting} \end{lstlisting}
C++ also has the structures C++ also contains the structures
\texttt{multiset} and \texttt{unordered\_multiset} \texttt{multiset} and \texttt{unordered\_multiset}
that work otherwise like \texttt{set} that work otherwise like \texttt{set}
and \texttt{unordered\_set} and \texttt{unordered\_set}
but they can contain multiple instances of an element. but they can contain multiple instances of an element.
For example, in the following code all three instances For example, in the following code all three instances
of the number 5 are added to the set: of the number 5 are added to a multiset:
\begin{lstlisting} \begin{lstlisting}
multiset<int> s; multiset<int> s;
@ -231,7 +231,7 @@ cout << s.count(5) << "\n"; // 3
\end{lstlisting} \end{lstlisting}
The function \texttt{erase} removes The function \texttt{erase} removes
all instances of an element all instances of an element
from a \texttt{multiset}: from a multiset:
\begin{lstlisting} \begin{lstlisting}
s.erase(5); s.erase(5);
cout << s.count(5) << "\n"; // 0 cout << s.count(5) << "\n"; // 0
@ -249,7 +249,7 @@ cout << s.count(5) << "\n"; // 2
A \key{map} is a generalized array A \key{map} is a generalized array
that consists of key-value-pairs. that consists of key-value-pairs.
While the keys in a regular array are always While the keys in an ordinary array are always
the consecutive integers $0,1,\ldots,n-1$, the consecutive integers $0,1,\ldots,n-1$,
where $n$ is the size of the array, where $n$ is the size of the array,
the keys in a map can be of any data type and the keys in a map can be of any data type and
@ -259,11 +259,11 @@ C++ contains two map implementations that
correspond to the set implementations: correspond to the set implementations:
the structure the structure
\texttt{map} is based on a balanced \texttt{map} is based on a balanced
binary tree and accessing an element binary tree and accessing elements
takes $O(\log n)$ time, takes $O(\log n)$ time,
while the structure while the structure
\texttt{unordered\_map} uses a hash map \texttt{unordered\_map} uses a hash map
and accessing an element takes $O(1)$ time on average. and accessing elements takes $O(1)$ time on average.
The following code creates a map The following code creates a map
where the keys are strings and the values are integers: where the keys are strings and the values are integers:
@ -288,15 +288,15 @@ is added to the map.
map<string,int> m; map<string,int> m;
cout << m["aybabtu"] << "\n"; // 0 cout << m["aybabtu"] << "\n"; // 0
\end{lstlisting} \end{lstlisting}
The function \texttt{count} determines The function \texttt{count} checks
if a key exists in the map: if a key exists in a map:
\begin{lstlisting} \begin{lstlisting}
if (m.count("aybabtu")) { if (m.count("aybabtu")) {
cout << "key exists in the map"; cout << "key exists in the map";
} }
\end{lstlisting} \end{lstlisting}
The following code prints all keys and values The following code prints all keys and values
in the map: in a map:
\begin{lstlisting} \begin{lstlisting}
for (auto x : m) { for (auto x : m) {
cout << x.first << " " << x.second << "\n"; cout << x.first << " " << x.second << "\n";
@ -358,7 +358,7 @@ reverse(v.begin(), v.end());
random_shuffle(v.begin(), v.end()); random_shuffle(v.begin(), v.end());
\end{lstlisting} \end{lstlisting}
These functions can also be used with a regular array. These functions can also be used with an ordinary array.
In this case, the functions are given pointers to the array In this case, the functions are given pointers to the array
instead of iterators: instead of iterators:
@ -371,8 +371,8 @@ random_shuffle(t, t+n);
\subsubsection{Set iterators} \subsubsection{Set iterators}
Iterators are often used when accessing Iterators are often used to access
elements in a set. elements of a set.
The following code creates an iterator The following code creates an iterator
\texttt{it} that points to the first element in the set: \texttt{it} that points to the first element in the set:
\begin{lstlisting} \begin{lstlisting}
@ -421,11 +421,11 @@ if (it == s.end()) cout << "x is missing";
\end{lstlisting} \end{lstlisting}
The function $\texttt{lower\_bound}(x)$ returns The function $\texttt{lower\_bound}(x)$ returns
an iterator to the smallest element in the set an iterator to the smallest element
whose value is \emph{at least} $x$, and whose value is \emph{at least} $x$, and
the function $\texttt{upper\_bound}(x)$ the function $\texttt{upper\_bound}(x)$
returns an iterator to the smallest element returns an iterator to the smallest element
in the set whose value is \emph{larger than} $x$. whose value is \emph{larger than} $x$.
If such elements do not exist, If such elements do not exist,
the return value of the functions will be \texttt{end}. the return value of the functions will be \texttt{end}.
These functions are not supported by the These functions are not supported by the
@ -487,18 +487,18 @@ cout << s[4] << "\n"; // 0
cout << s[5] << "\n"; // 1 cout << s[5] << "\n"; // 1
\end{lstlisting} \end{lstlisting}
The benefit in using a bitset is that The benefit in using bitsets is that
it requires less memory than a regular array, they require less memory than ordinary arrays,
because each element in the bitset only because each element in a bitset only
uses one bit of memory. uses one bit of memory.
For example, For example,
if $n$ bits are stored as an \texttt{int} array, if $n$ bits are stored in an \texttt{int} array,
$32n$ bits of memory will be used, $32n$ bits of memory will be used,
but a corresponding bitset only requires $n$ bits of memory. but a corresponding bitset only requires $n$ bits of memory.
In addition, the values in a bitset In addition, the values of a bitset
can be efficiently manipulated using can be efficiently manipulated using
bit operators, which makes it possible to bit operators, which makes it possible to
optimize algorithms. optimize algorithms using bit sets.
The following code shows another way to create a bitset: The following code shows another way to create a bitset:
\begin{lstlisting} \begin{lstlisting}
@ -530,9 +530,9 @@ cout << (a^b) << "\n"; // 1001101110
A \texttt{deque} is a dynamic array A \texttt{deque} is a dynamic array
whose size can be changed at both ends of the array. whose size can be changed at both ends of the array.
Like a vector, a deque contains functions Like a vector, a deque contains the functions
\texttt{push\_back} and \texttt{pop\_back}, but \texttt{push\_back} and \texttt{pop\_back}, but
it also contains additional functions it also contains the functions
\texttt{push\_front} and \texttt{pop\_front} \texttt{push\_front} and \texttt{pop\_front}
that are not available in a vector. that are not available in a vector.
@ -547,7 +547,7 @@ d.pop_front(); // [5]
\end{lstlisting} \end{lstlisting}
The internal implementation of a deque The internal implementation of a deque
is more complex than the implementation of a vector. is more complex than that of a vector.
For this reason, a deque is slower than a vector. For this reason, a deque is slower than a vector.
Still, the time complexity of adding and removing Still, the time complexity of adding and removing
elements is $O(1)$ on average at both ends. elements is $O(1)$ on average at both ends.
@ -583,7 +583,7 @@ provides two $O(1)$ time operations:
adding a element to the end of the queue, adding a element to the end of the queue,
and removing the first element in the queue. and removing the first element in the queue.
It is only possible to access the first It is only possible to access the first
and the last element of a queue. and last element of a queue.
The following code shows how a queue can be used: The following code shows how a queue can be used:
\begin{lstlisting} \begin{lstlisting}
@ -606,7 +606,7 @@ maintains a set of elements.
The supported operations are insertion and, The supported operations are insertion and,
depending on the type of the queue, depending on the type of the queue,
retrieval and removal of retrieval and removal of
either the minimum element or the maximum element. either the minimum or maximum element.
The time complexity is $O(\log n)$ The time complexity is $O(\log n)$
for insertion and removal and $O(1)$ for retrieval. for insertion and removal and $O(1)$ for retrieval.
@ -623,7 +623,7 @@ As default, the elements in the C++
priority queue are sorted in decreasing order, priority queue are sorted in decreasing order,
and it is possible to find and remove the and it is possible to find and remove the
largest element in the queue. largest element in the queue.
The following code shows an example: The following code illustrates this:
\begin{lstlisting} \begin{lstlisting}
priority_queue<int> q; priority_queue<int> q;
@ -643,7 +643,8 @@ q.pop();
Using the following declaration, Using the following declaration,
we can create a priority queue we can create a priority queue
that supports finding and removing the minimum element: that allows us to find and remove
the minimum element:
\begin{lstlisting} \begin{lstlisting}
priority_queue<int,vector<int>,greater<int>> q; priority_queue<int,vector<int>,greater<int>> q;
@ -670,20 +671,20 @@ and 9 belong to both of the lists.
A straightforward solution to the problem is A straightforward solution to the problem is
to go through all pairs of numbers in $O(n^2)$ time, to go through all pairs of numbers in $O(n^2)$ time,
but next we will concentrate on but next we will concentrate on
more efficient solutions. more efficient algorithms.
\subsubsection{Solution 1} \subsubsection{Algorithm 1}
We construct a set of the numbers in $A$, We construct a set of the numbers that appear in $A$,
and after this, we iterate through the numbers and after this, we iterate through the numbers
in $B$ and check for each number if it in $B$ and check for each number if it
also belongs to $A$. also belongs to $A$.
This is efficient because the numbers in $A$ This is efficient because the numbers of $A$
are in a set. are in a set.
Using the \texttt{set} structure, Using the \texttt{set} structure,
the time complexity of the algorithm is $O(n \log n)$. the time complexity of the algorithm is $O(n \log n)$.
\subsubsection{Solution 2} \subsubsection{Algorithm 2}
It is not needed to maintain an ordered set, It is not needed to maintain an ordered set,
so instead of the \texttt{set} structure so instead of the \texttt{set} structure
@ -693,7 +694,7 @@ more efficient, because we only have to change
the underlying data structure. the underlying data structure.
The time complexity of the new algorithm is $O(n)$. The time complexity of the new algorithm is $O(n)$.
\subsubsection{Solution 3} \subsubsection{Algorithm 3}
Instead of data structures, we can use sorting. Instead of data structures, we can use sorting.
First, we sort both lists $A$ and $B$. First, we sort both lists $A$ and $B$.
@ -707,12 +708,12 @@ so the total time complexity is $O(n \log n)$.
The following table shows how efficient The following table shows how efficient
the above algorithms are when $n$ varies and the above algorithms are when $n$ varies and
the elements in the lists are random the elements of the lists are random
integers between $1 \ldots 10^9$: integers between $1 \ldots 10^9$:
\begin{center} \begin{center}
\begin{tabular}{rrrr} \begin{tabular}{rrrr}
$n$ & solution 1 & solution 2 & solution 3 \\ $n$ & algorithm 1 & algorithm 2 & algorithm 3 \\
\hline \hline
$10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\ $10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\
$2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\ $2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\
@ -722,22 +723,22 @@ $5 \cdot 10^6$ & $10{,}0$ s & $2{,}3$ s & $0{,}9$ s \\
\end{tabular} \end{tabular}
\end{center} \end{center}
Solutions 1 and 2 are equal except that Algorithm 1 and 2 are equal except that
they use different set structures. they use different set structures.
In this problem, this choice has an important effect on In this problem, this choice has an important effect on
the running time, because solution 2 the running time, because algorithm 2
is 45 times faster than solution 1. is 45 times faster than algorithm 1.
However, the most efficient solution is solution 3 However, the most efficient algorithm is algorithm 3
that uses sorting. that uses sorting.
It only uses half of the time compared to solution 2. It only uses half of the time compared to algorithm 2.
Interestingly, the time complexity of both Interestingly, the time complexity of both
solution 1 and solution 3 is $O(n \log n)$, algorithm 1 and algorithm 3 is $O(n \log n)$,
but despite this, solution 3 is ten times faster. but despite this, algorithm 3 is ten times faster.
This can be explained by the fact that This can be explained by the fact that
sorting is a simple procedure and it is done sorting is a simple procedure and it is done
only once at the beginning of solution 3, only once at the beginning of algorithm 3,
and the rest of the algorithm works in linear time. and the rest of the algorithm works in linear time.
On the other hand, On the other hand,
solution 3 maintains a complex balanced binary tree algorithm 3 maintains a complex balanced binary tree
during the whole algorithm. during the whole algorithm.