Corrections

This commit is contained in:
Antti H S Laaksonen 2017-02-11 21:24:28 +02:00
parent b2849c6165
commit 1e4c77253c
1 changed files with 147 additions and 140 deletions

View File

@ -10,28 +10,25 @@ but worse than $O(\log n)$.
Still, many square root algorithms are fast in practice Still, many square root algorithms are fast in practice
and have small constant factors. and have small constant factors.
As an example, let's consider the problem of As an example, let us consider the problem of
handling sum queries in an array. creating a data structure that supports
The required operations are: two operations in an array:
modifying an element at a given position
\begin{itemize} and calculating the sum of elements in the given range.
\item change the value at index $x$
\item calculate the sum in the range $[a,b]$
\end{itemize}
We have previously solved the problem using We have previously solved the problem using
a binary indexed tree and a segment tree, a binary indexed tree and a segment tree,
that support both operations in $O(\log n)$ time. that support both operations in $O(\log n)$ time.
However, now we will solve the problem However, now we will solve the problem
in another way using a square root structure in another way using a square root structure
so that we can calculate sums in $O(\sqrt n)$ time that allows us to modify elements in $O(1)$ time
and modify values in $O(1)$ time. and calculate sums in $O(\sqrt n)$ time.
The idea is to divide the array into blocks The idea is to divide the array into blocks
of size $\sqrt n$ so that each block contains of size $\sqrt n$ so that each block contains
the sum of elements inside the block. the sum of elements inside the block.
The following example shows an array and the For example, an array of 16 elements will be
corresponding segments: divided into blocks of 4 elements as follows:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -67,9 +64,15 @@ corresponding segments:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
When a value in the array changes, Using this structure,
we have to calculate the sum of the corresponding it is easy to modify the array,
block again: because it is only needed to calculate
the sum of a single block again
after each modification,
which can be done in $O(1)$ time.
For example, the following picture shows
how the value of an element and
the sum of the corresponding block change:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -107,9 +110,12 @@ block again:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
Any sum in the array can be calculated as a combination Calculating the sum of elements in a range is
of single values in the array and the sums of the a bit more difficult.
blocks between them: It turns out that we can always divide
the range into three parts such that
the sum consists of values of single elements
and sums of blocks between them:
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
@ -152,35 +158,23 @@ blocks between them:
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
We can change a value in $O(1)$ time, Since the number of single elements is $O(\sqrt n)$
because we only have to change the sum of a single block. and also the number of blocks is $O(\sqrt n)$,
A sum in a range consists of three parts: the time complexity of the sum query is $O(\sqrt n)$.
Thus, the parameter $\sqrt n$ balances two things:
the array is divided into $\sqrt n$ blocks,
each of which contains $\sqrt n$ elements.
\begin{itemize} In practice, it is not needed to use the
\item first, there are $O(\sqrt n)$ single values exact parameter $\sqrt n$, but it may be better to
\item then, there are $O(\sqrt n)$ consecutive blocks
\item finally, there are $O(\sqrt n)$ single values
\end{itemize}
Calculating each sum takes $O(\sqrt n)$ time,
so the total complexity for calculating the sum
of values in any range is $O(\sqrt n)$.
The reason why we use the parameter $\sqrt n$ is that
it balances two things:
for example, an array of $n$ elements is divided
into $\sqrt n$ blocks, each of which contains
$\sqrt n$ elements.
In practice, it is not needed to use exactly
the parameter $\sqrt n$ in algorithms, but it may be better to
use parameters $k$ and $n/k$ where $k$ is use parameters $k$ and $n/k$ where $k$ is
larger or smaller than $\sqrt n$. larger or smaller than $\sqrt n$.
The optimal parameter depends on the problem and input.
The best parameter depends on the problem For example, if an algorithm often goes
and input. through the blocks but rarely inspects
For example, if an algorithm often goes through single elements inside the blocks,
blocks but rarely iterates elements inside it may be a good idea to divide the array into
blocks, it may be good to divide the array into
$k < \sqrt n$ blocks, each of which contains $n/k > \sqrt n$ $k < \sqrt n$ blocks, each of which contains $n/k > \sqrt n$
elements. elements.
@ -188,24 +182,19 @@ elements.
\index{batch processing} \index{batch processing}
In \key{batch processing}, the operations of an Sometimes the operations of an algorithm
algorithm are divided into batches, can be divided into batches so that
and each batch will be processed separately. each batch can be processed separately.
Between the batches some precalculation is done Some precalculation is done
to process the future operations more efficiently. between the batches
in order to process the future operations more efficiently.
If there are $O(\sqrt n)$ batches of size $O(\sqrt n)$,
this results in a square root algorithm.
In a square root algorithm, $n$ operations are As an example, let us consider a problem
divided into batches of size $O(\sqrt n)$,
and the number of both batches and operations in each
batch is $O(\sqrt n)$.
This balances the precalculation time between
the batches and the time needed for processing
the batches.
As an example, let's consider a problem
where a grid of size $k \times k$ where a grid of size $k \times k$
initially consists of white squares. initially consists of white squares,
Our task is to perform $n$ operations, and our task is to perform $n$ operations,
each of which is one of the following: each of which is one of the following:
\begin{itemize} \begin{itemize}
\item \item
@ -217,7 +206,8 @@ between squares $(y_1,x_1)$ and $(y_2,x_2)$
is $|y_1-y_2|+|x_1-x_2|$ is $|y_1-y_2|+|x_1-x_2|$
\end{itemize} \end{itemize}
The solution is to divide the operations into We can solve the problem by dividing
the operations into
$O(\sqrt n)$ batches, each of which consists $O(\sqrt n)$ batches, each of which consists
of $O(\sqrt n)$ operations. of $O(\sqrt n)$ operations.
At the beginning of each batch, At the beginning of each batch,
@ -227,81 +217,99 @@ This can be done in $O(k^2)$ time using breadth-first search.
When processing a batch, we maintain a list of squares When processing a batch, we maintain a list of squares
that have been painted black in the current batch. that have been painted black in the current batch.
Now, the distance from a square to the nearest black The list contains $O(\sqrt n)$ elements,
because there are $O(\sqrt n)$ operations in each batch.
Thus, the distance from a square to the nearest black
square is either the precalculated distance or the distance square is either the precalculated distance or the distance
to a square that has been painted black in the current batch. to a square that has been painted black in the current batch.
The algorithm works in The algorithm works in
$O((k^2+n) \sqrt n)$ time. $O((k^2+n) \sqrt n)$ time.
First, between the batches, First, there are $O(\sqrt n)$ breadth-first searches
there are $O(\sqrt n)$ searches that each take and each search takes $O(k^2)$ time.
$O(k^2)$ time. Second, the total number of
Second, the total number of processed squares processed during the algorithm
squares is $O(n)$, and at each square, is $O(n)$, and at each square,
we go through a list of $O(\sqrt n)$ squares we go through a list of $O(\sqrt n)$ squares.
in a batch.
If the algorithm would perform a breadth-first search If the algorithm would perform a breadth-first search
at each operation, the complexity would be at each operation, the time complexity would be
$O(k^2 n)$. $O(k^2 n)$.
And if the algorithm would go through all painted And if the algorithm would go through all painted
squares at each operation, squares at each operation,
the complexity would be $O(n^2)$. the time complexity would be $O(n^2)$.
The square root algorithm combines these complexities, Thus, the time complexity of the square root algorithm
and turns the factor $n$ into $\sqrt n$. is a combination of these time complexities,
but in addition, a factor $n$ is replaced by $\sqrt n$.
\section{Case processing} \section{Subalgorithms}
\index{case processing} Some square root algorithms consists of
subalgorithms that are specialized for different
input parameters.
Typically, there are two subalgorithms:
one algorithm is efficient when
some parameter is smaller than $\sqrt n$,
and another algorithm is efficient
when the parameter is larger than $\sqrt n$.
In \key{case processing}, an algorithm has As an example, let us consider a problem where
specialized subalgorithms for different cases that we are given a tree of $n$ nodes,
may appear during the algorithm.
Typically, one part is efficient for
small parameters, and another part is efficient
for large parameters, and the turning point is
about $\sqrt n$.
As an example, let's consider a problem where
we are given a tree that contains $n$ nodes,
each with some color. Our task is to find two nodes each with some color. Our task is to find two nodes
that have the same color and the distance that have the same color and whose distance
between them is as large as possible. is as large as possible.
The problem can be solved by going through all For example, in the following tree,
colors one after another, and for each color, the maximum distance is 4 between
finding two nodes of that color whose distance is the red nodes 3 and 4:
maximum.
For a fixed color, a subalgorithm will be used
that depends on the number of nodes of that color.
Let's assume that the current color is $x$
and there are $c$ nodes whose color is $x$.
There are two cases:
\subsubsection*{Case 1: $c \le \sqrt n$} \begin{center}
\begin{tikzpicture}[scale=0.9]
\node[draw, circle, fill=green!40] (1) at (1,3) {$2$};
\node[draw, circle, fill=red!40] (2) at (4,3) {$3$};
\node[draw, circle, fill=red!40] (3) at (1,1) {$5$};
\node[draw, circle, fill=blue!40] (4) at (4,1) {$6$};
\node[draw, circle, fill=red!40] (5) at (-2,1) {$4$};
\node[draw, circle, fill=blue!40] (6) at (-2,3) {$1$};
\path[draw,thick,-] (1) -- (2);
\path[draw,thick,-] (1) -- (3);
\path[draw,thick,-] (3) -- (4);
\path[draw,thick,-] (3) -- (6);
\path[draw,thick,-] (5) -- (6);
\end{tikzpicture}
\end{center}
The problem can be solved by going through
all colors and calculating
the maximum distance of two nodes for each color
separately.
Assume that the current color is $x$ and
there are $c$ nodes whose color is $x$.
There are two subalgorithms
that are specialized for small and large
values of $c$:
\emph{Case 1}: $c \le \sqrt n$.
If the number of nodes is small, If the number of nodes is small,
we go through all pairs of nodes whose we go through all pairs of nodes whose
color is $x$ and select the pair that color is $x$ and select the pair that
has the maximum distance. has the maximum distance.
For each node, we have calculate the distance For each node, it is needed to calculate the distance
to $O(\sqrt n)$ other nodes (see 18.3), to $O(\sqrt n)$ other nodes (see 18.3),
so the total time needed for processing all so the total time needed for processing all
nodes in case 1 is $O(n \sqrt n)$. nodes is $O(n \sqrt n)$.
\subsubsection*{Case 2: $c > \sqrt n$}
\emph{Case 2}: $c > \sqrt n$.
If the number of nodes is large, If the number of nodes is large,
we traverse through the whole tree we go through the whole tree
and calculate the maximum distance between and calculate the maximum distance between
two nodes with color $x$. two nodes with color $x$.
The time complexity of the tree traversal is $O(n)$, The time complexity of the tree traversal is $O(n)$,
and this will be done at most $O(\sqrt n)$ times, and this will be done at most $O(\sqrt n)$ times,
so the total time needed for case 2 is so the total time needed is $O(n \sqrt n)$.
$O(n \sqrt n)$.\\\\
\noindent
The time complexity of the algorithm is $O(n \sqrt n)$, The time complexity of the algorithm is $O(n \sqrt n)$,
because both case 1 and case 2 take $O(n \sqrt n)$ time. because both cases take $O(n \sqrt n)$ time.
\section{Mo's algorithm} \section{Mo's algorithm}
@ -312,54 +320,53 @@ that require processing range queries in
a \emph{static} array. a \emph{static} array.
Before processing the queries, the algorithm Before processing the queries, the algorithm
sorts them in a special order which guarantees sorts them in a special order which guarantees
that the algorithm runs efficiently. that the algorithm works efficiently.
At each moment in the algorithm, there is an active At each moment in the algorithm, there is an active
subarray and the algorithm maintains the answer range and the algorithm maintains the answer
for a query to that subarray. to a query related to that range.
The algorithm processes the given queries one by one, The algorithm processes the queries one by one,
and always changes the active subarray and always updates the endpoints of the
by inserting and removing elements active range by inserting and removing elements.
so that it corresponds to the current query.
The time complexity of the algorithm is The time complexity of the algorithm is
$O(n \sqrt n f(n))$ when there are $n$ queries $O(n \sqrt n f(n))$ when there are $n$ queries
and each insertion and removal of an element and each insertion and removal of an element
takes $O(f(n))$ time. takes $O(f(n))$ time.
The essential trick in Mo's algorithm is that The trick in Mo's algorithm is the order
the queries are processed in a special order, in which the queries are processed:
which makes the algorithm efficient. The array is divided into blocks of $O(\sqrt n)$
The array is divided into blocks of $k=O(\sqrt n)$
elements, and the queries are sorted primarily by elements, and the queries are sorted primarily by
the index of the block that contains the first element the number of the block that contains the first element
of the query, and secondarily by the index of the in the range, and secondarily by the position of the
last element of the query. last element in the range.
It turns out that using this order, the algorithm It turns out that using this order, the algorithm
only performs $O(n \sqrt n)$ operations, only performs $O(n \sqrt n)$ operations,
because the left border of the subarray moves because the left endpoint of the range moves
$n$ times $O(\sqrt n)$ steps, $n$ times $O(\sqrt n)$ steps,
and the right border of the subarray moves and the right endpoint of the range moves
$\sqrt n$ times $O(n)$ steps. Thus, both the $\sqrt n$ times $O(n)$ steps. Thus, both
borders move a total of $O(n \sqrt n)$ steps. endpoints move a total of $O(n \sqrt n)$ steps during the algorithm.
\subsubsection*{Example} \subsubsection*{Example}
As an example, let's consider a problem As an example, consider a problem
where we are given a set of subarrays in an array, where we are given a set of queries,
and our task is to calculate for each subarray each of them corresponding to a range in an array,
the number of distinct elements in the subarray. and our task is to calculate for each query
the number of distinct elements in the range.
In Mo's algorithm, the queries are always sorted In Mo's algorithm, the queries are always sorted
in the same way, but the way the answer for the query in the same way, but it depends on the problem
is maintained depends on the problem. how the answer to the query is maintained.
In this problem, we can maintain an array In this problem, we can maintain an array
\texttt{c} where $\texttt{c}[x]$ \texttt{c} where $\texttt{c}[x]$
indicates how many times an element $x$ indicates how many times an element $x$
occurs in the active subarray. occurs in the active range.
When we move from a query to another query, When we move from one query to another query,
the active subarray changes. the active range changes.
For example, if the current subarray is For example, if the current range is
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (1,0) rectangle (5,1); \fill[color=lightgray] (1,0) rectangle (5,1);
@ -375,7 +382,7 @@ For example, if the current subarray is
\node at (8.5, 0.5) {4}; \node at (8.5, 0.5) {4};
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
and the next subarray is and the next range is
\begin{center} \begin{center}
\begin{tikzpicture}[scale=0.7] \begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (2,0) rectangle (7,1); \fill[color=lightgray] (2,0) rectangle (7,1);
@ -392,21 +399,21 @@ and the next subarray is
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
there will be three steps: there will be three steps:
the left border moves one step to the left, the left endpoint moves one step to the left,
and the right border moves two steps to the right. and the right endpoint moves two steps to the right.
After each step, we update the After each step, we update the
array \texttt{c}. array \texttt{c}.
If an element $x$ is added to the subarray, If an element $x$ is added to the range,
the value the value
$\texttt{c}[x]$ increases by one, $\texttt{c}[x]$ increases by one,
and if an element $x$ is removed from the subarray, and if an element $x$ is removed from the range,
the value $\texttt{c}[x]$ decreases by one. the value $\texttt{c}[x]$ decreases by one.
If after an insertion If after an insertion
$\texttt{c}[x]=1$, $\texttt{c}[x]=1$,
the answer for the query increases by one, the answer to the query will be increased by one,
and if after a removal $\texttt{c}[x]=0$, and if after a removal $\texttt{c}[x]=0$,
the answer for the query decreases by one. the answer to the query will be decreased by one.
In this problem, the time needed to perform In this problem, the time needed to perform
each step is $O(1)$, so the total time complexity each step is $O(1)$, so the total time complexity