Lazy segment tree
This commit is contained in:
parent
454dea9d95
commit
4994a68057
317
luku28.tex
317
luku28.tex
|
@ -1,29 +1,22 @@
|
||||||
\chapter{Segment trees revisited}
|
\chapter{Segment trees revisited}
|
||||||
|
|
||||||
\index{segmenttipuu@segmenttipuu}
|
\index{segment tree}
|
||||||
|
|
||||||
Segmenttipuu on tehokas tietorakenne,
|
A segment tree is a versatile data structure
|
||||||
joka mahdollistaa monenlaisten
|
that can be used in many different situations.
|
||||||
kyselyiden toteuttamisen tehokkaasti.
|
However, there are many topics related to segment trees
|
||||||
Tähän mennessä olemme käyttäneet
|
that we haven't touched yet.
|
||||||
kuitenkin segmenttipuuta melko rajoittuneesti.
|
Now it's time to learn some more advanced variations
|
||||||
Nyt on aika tutustua pintaa syvemmältä
|
of segment trees and see their full potential.
|
||||||
segmenttipuun mahdollisuuksiin.
|
|
||||||
|
|
||||||
Tähän mennessä olemme kulkeneet segmenttipuuta
|
So far, we have implemented the operations
|
||||||
\textit{alhaalta ylöspäin} lehdistä juureen.
|
of a segment tree by walking \emph{from the bottom to the top},
|
||||||
Vaihtoehtoinen tapa toteuttaa puun käsittely
|
from the leaves to the root.
|
||||||
on kulkea \textit{ylhäältä alaspäin} juuresta lehtiin.
|
For example, we have calculated the sum of a range $[a,b]$
|
||||||
Tämä kulkusuunta on usein kätevä silloin,
|
as follows (Chapter 9.3):
|
||||||
kun kyseessä on perustilannetta
|
|
||||||
monimutkaisempi segmenttipuu.
|
|
||||||
|
|
||||||
Esimerkiksi välin $[a,b]$ summan laskeminen
|
|
||||||
segmenttipuussa tapahtuu alhaalta ylöspäin
|
|
||||||
tuttuun tapaan näin (luku 9.3):
|
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int summa(int a, int b) {
|
int sum(int a, int b) {
|
||||||
a += N; b += N;
|
a += N; b += N;
|
||||||
int s = 0;
|
int s = 0;
|
||||||
while (a <= b) {
|
while (a <= b) {
|
||||||
|
@ -34,51 +27,48 @@ int summa(int a, int b) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
Ylhäältä alaspäin toteutettuna funktiosta tulee:
|
However, in more advanced segment trees,
|
||||||
|
it's beneficial to implement the operations
|
||||||
|
in another way, \emph{from the top to the bottom},
|
||||||
|
from the root to the leaves.
|
||||||
|
Using this approach, the function becomes as follows:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int summa(int a, int b, int k, int x, int y) {
|
int sum(int a, int b, int k, int x, int y) {
|
||||||
if (b < x || a > y) return 0;
|
if (b < x || a > y) return 0;
|
||||||
if (a == x && b == y) return p[k];
|
if (a == x && b == y) return p[k];
|
||||||
int d = (y-x+1)/2;
|
int d = (y-x+1)/2;
|
||||||
return summa(a, min(x+d-1,b), 2*k, x, x+d-1) +
|
return sum(a, min(x+d-1,b), 2*k, x, x+d-1) +
|
||||||
summa(max(x+d,a), b, 2*k+1, x+d, y);
|
sum(max(x+d,a), b, 2*k+1, x+d, y);
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
Nyt välin $[a,b]$ summan saa laskettua
|
Now we can calulate the sum of the range $[a,b]$
|
||||||
kutsumalla funktiota näin:
|
as follows:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int s = summa(a, b, 1, 0, N-1);
|
int s = sum(a, b, 1, 0, N-1);
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
The parameter $k$ is the current position
|
||||||
|
in array \texttt{p}.
|
||||||
|
Initially $k$ equals 1, because we begin
|
||||||
|
at the root of the segment tree.
|
||||||
|
The range $[x,y]$ corresponds to $k$,
|
||||||
|
and is initially $[0,N-1]$.
|
||||||
|
If $[a,b]$ is outside $[x,y]$,
|
||||||
|
the sum of the range is 0,
|
||||||
|
and if $[a,b]$ equals $[x,y]$,
|
||||||
|
the sum can be found in array \texttt{p}.
|
||||||
|
If $[a,b]$ is completely or partially inside $[x,y]$,
|
||||||
|
the search continues recursively to the
|
||||||
|
left and right half of $[x,y]$.
|
||||||
|
The size of both halves is $d=\frac{1}{2}(y-x+1)$;
|
||||||
|
the left half is $[x,x+d-1]$
|
||||||
|
and the right half is $[x+d,y]$.
|
||||||
|
|
||||||
Parametri $k$ ilmaisee kohdan
|
The following picture shows how the search proceeds
|
||||||
taulukossa \texttt{p}.
|
when calculating the sum of the marked elements.
|
||||||
Aluksi $k$:n arvona on 1,
|
The gray nodes indicate nodes where the recursion
|
||||||
koska summan laskeminen alkaa
|
stops and the sum of the range can be found in array \texttt{p}.
|
||||||
segmenttipuun juuresta.
|
|
||||||
|
|
||||||
Väli $[x,y]$ on parametria $k$ vastaava väli,
|
|
||||||
aluksi koko kyselyalue eli $[0,N-1]$.
|
|
||||||
Jos väli $[a,b]$ on välin $[x,y]$
|
|
||||||
ulkopuolella, välin summa on 0.
|
|
||||||
Jos taas välit $[a,b]$ ja $[x,y]$
|
|
||||||
ovat samat, summan saa taulukosta \texttt{p}.
|
|
||||||
|
|
||||||
Jos väli $[a,b]$ on kokonaan tai osittain välin $[x,y]$
|
|
||||||
sisällä, haku jatkuu rekursiivisesti
|
|
||||||
välin $[x,y]$ vasemmasta ja oikeasta puoliskosta.
|
|
||||||
Kummankin puoliskon koko on $d=\frac{1}{2}(y-x+1)$,
|
|
||||||
joten vasen puolisko kattaa välin $[x,x+d-1]$
|
|
||||||
ja oikea puolisko kattaa välin $[x+d,y]$.
|
|
||||||
|
|
||||||
Seuraava kuva näyttää,
|
|
||||||
kuinka haku etenee puussa,
|
|
||||||
kun lasketaan puun alle
|
|
||||||
merkityn välin summa.
|
|
||||||
Harmaat solmut ovat kohtia,
|
|
||||||
joissa rekursio päättyy ja välin
|
|
||||||
summan saa taulukosta \texttt{p}.
|
|
||||||
\\
|
\\
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=0.7]
|
\begin{tikzpicture}[scale=0.7]
|
||||||
|
@ -169,59 +159,61 @@ summan saa taulukosta \texttt{p}.
|
||||||
\draw [decoration={brace}, decorate, line width=0.5mm] (14,-0.25) -- (5,-0.25);
|
\draw [decoration={brace}, decorate, line width=0.5mm] (14,-0.25) -- (5,-0.25);
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
Myös tässä toteutuksessa kyselyn aikavaativuus on $O(\log n)$,
|
Also in this implementation,
|
||||||
koska haun aikana käsiteltävien solmujen määrä on $O(\log n)$.
|
the time complexity of a range query is $O(\log n)$,
|
||||||
|
because the total number of processed nodes is $O(\log n)$.
|
||||||
|
|
||||||
\section{Laiska eteneminen}
|
\section{Lazy propagation}
|
||||||
|
|
||||||
\index{laiska eteneminen@laiska eteneminen}
|
\index{lazy propagation}
|
||||||
\index{laiska segmenttipuu@laiska segmenttipuu}
|
\index{lazy segment tree}
|
||||||
|
|
||||||
\key{Laiska eteneminen}
|
Using \key{lazy propagation}, we can construct
|
||||||
mahdollistaa segmenttipuun,
|
a segment tree that supports both range updates
|
||||||
jossa voi sekä muuttaa väliä että kysyä tietoa väliltä
|
and range queries in $O(\log n)$ time.
|
||||||
ajassa $O(\log n)$.
|
The idea is to perform the updates and queries
|
||||||
Ideana on suorittaa muutokset ja kyselyt ylhäältä
|
from the top to the bottom, and process the updates
|
||||||
alaspäin ja toteuttaa muutokset laiskasti niin,
|
\emph{lazily} so that they are propagated
|
||||||
että ne välitetään puussa alaspäin vain silloin,
|
down the tree only when it is necessary.
|
||||||
kun se on välttämätöntä.
|
|
||||||
|
|
||||||
Laiskassa segmenttipuussa solmuihin liittyy
|
In a lazy segment tree, nodes contain two types of
|
||||||
kahdenlaista tietoa.
|
information.
|
||||||
Kuten tavallisessa segmenttipuussa,
|
Like in a normal segment tree,
|
||||||
jokaisessa solmussa on sitä vastaavan välin
|
each node contains the sum or some other value
|
||||||
summa tai muu haluttu tieto.
|
of the corresponding subarray.
|
||||||
Tämän lisäksi solmussa voi olla laiskaan etenemiseen
|
In addition, the node may contain information
|
||||||
liittyvää tietoa, jota ei ole vielä välitetty
|
related to lazy updates, which has not been
|
||||||
solmusta alaspäin.
|
propagated yet to its children.
|
||||||
|
|
||||||
Välin muutostapa voi olla joko
|
There are two possible types for range updates:
|
||||||
\textit{lisäys} tai \textit{asetus}.
|
\emph{addition} and \emph{insertion}.
|
||||||
Lisäyksessä välin jokaiseen alkioon lisätään
|
In addition, each element in the range is
|
||||||
tietty arvo, ja asetuksessa välin
|
increased by some value,
|
||||||
jokainen alkio saa tietyn arvon.
|
and in insertion, each element in the range
|
||||||
Kummankin operaation toteutus on melko samanlainen,
|
is assigned some value.
|
||||||
ja puu voi myös sallia samaan aikaan
|
Both operations can be implemented using
|
||||||
molemmat muutostavat.
|
similar ideas, and it's possible to construct
|
||||||
|
a tree that supports both the operations
|
||||||
|
simultaneously.
|
||||||
|
|
||||||
\subsubsection{Laiska segmenttipuu}
|
\subsubsection{Lazy segment tree}
|
||||||
|
|
||||||
|
Let's consider an example where our goal is to
|
||||||
|
construct a segment tree that supports the following operations:
|
||||||
|
|
||||||
Tarkastellaan esimerkkinä tilannetta,
|
|
||||||
jossa segmenttipuun
|
|
||||||
tulee toteuttaa seuraavat operaatiot:
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item lisää jokaisen välin $[a,b]$ alkioon arvo $u$
|
\item increase each element in $[a,b]$ by $u$
|
||||||
\item laske välin $[a,b]$ alkioiden summa
|
\item calculate the sum of elements in $[a,b]$
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
Toteutamme puun, jonka jokaisessa
|
We will construct a tree where each node
|
||||||
solmussa on kaksi arvoa $s/z$:
|
contains two values $s/z$:
|
||||||
välin lukujen summa $s$,
|
$s$ denotes the sum of elements in the range,
|
||||||
kuten tavallisessa segmenttipuussa, sekä
|
like in a standard segment tree,
|
||||||
laiska muutos $z$,
|
and $z$ denotes a lazy update,
|
||||||
joka tarkoittaa,
|
which means that all elements in the range
|
||||||
että kaikkiin välin lukuihin tulee lisätä $z$.
|
should be increased by $z$.
|
||||||
Seuraavassa puussa jokaisessa solmussa $z=0$
|
In the following tree, $z=0$ for all nodes,
|
||||||
eli mitään muutoksia ei ole kesken.
|
so there are no lazy updates.
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=0.7]
|
\begin{tikzpicture}[scale=0.7]
|
||||||
\draw (0,0) grid (16,1);
|
\draw (0,0) grid (16,1);
|
||||||
|
@ -294,19 +286,19 @@ eli mitään muutoksia ei ole kesken.
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Kun välin $[a,b]$ solmuja kasvatetaan $u$:lla,
|
When a range $[a,b]$ is increased by $u$,
|
||||||
alkaa kulku puun juuresta lehtiä kohti.
|
we walk from the root towards the leaves
|
||||||
Kulun aikana tapahtuu kahdenlaisia muutoksia puun solmuihin:
|
and modify the nodes in the tree as follows:
|
||||||
|
If the range $[x,y]$ of a node is
|
||||||
|
completely inside the range $[a,b]$,
|
||||||
|
we increase the $z$ value of the node by $u$ and stop.
|
||||||
|
However, if $[x,y]$ only partially belongs to $[a,b]$,
|
||||||
|
we increase the $s$ value of the node by $hu$,
|
||||||
|
where $h$ is the size of the intersection of $[a,b]$
|
||||||
|
and $[x,y]$, and continue our walk recursively in the tree.
|
||||||
|
|
||||||
Jos solmun väli $[x,y]$ kuuluu kokonaan
|
For example, the following picture shows the tree after
|
||||||
muutettavalle välille $[a,b]$,
|
increasing the elements in the range marked at the bottom by 2:
|
||||||
solmun $z$-arvo kasvaa $u$:llä ja kulku pysähtyy.
|
|
||||||
Jos taas väli $[x,y]$ kuuluu osittain välille $[a,b]$,
|
|
||||||
solmun $s$-arvo kasvaa $hu$:llä,
|
|
||||||
missä $h$ on välien $[a,b]$ ja $[x,y]$ yhteisen osan pituus,
|
|
||||||
ja kulku jatkuu rekursiivisesti alaspäin.
|
|
||||||
|
|
||||||
Kasvatetaan esimerkiksi puun alle merkittyä väliä 2:lla:
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=0.7]
|
\begin{tikzpicture}[scale=0.7]
|
||||||
\fill[color=gray!50] (5,0) rectangle (6,1);
|
\fill[color=gray!50] (5,0) rectangle (6,1);
|
||||||
|
@ -395,23 +387,24 @@ Kasvatetaan esimerkiksi puun alle merkittyä väliä 2:lla:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Välin $[a,b]$ summan laskenta tapahtuu myös
|
We also calculate the sum in a range $[a,b]$
|
||||||
kulkuna puun juuresta lehtiä kohti.
|
by walking in the tree from the root towards the leaves.
|
||||||
Jos solmun väli $[x,y]$ kuuluu kokonaan väliin $[a,b]$,
|
If the range $[x,y]$ of a node completely belongs
|
||||||
kyselyn summaan lisätään solmun $s$-arvo
|
to $[a,b]$, we add the $s$ value of the node to the sum.
|
||||||
sekä mahdollinen $z$-arvon tuottama lisäys.
|
Otherwise, we continue the search recursively
|
||||||
Muussa tapauksessa kulku jatkuu rekursiivisesti alaspäin solmun lapsiin.
|
downwards in the tree.
|
||||||
|
|
||||||
Aina ennen solmun käsittelyä siinä mahdollisesti
|
Always before processing a node,
|
||||||
oleva laiska muutos välitetään tasoa alemmas.
|
the value of the lazy update is propagated
|
||||||
Tämä tapahtuu sekä välin muutoskyselyssä
|
to the children of the node.
|
||||||
että summakyselyssä.
|
This happens both in a range update
|
||||||
Ideana on, että laiska muutos etenee alaspäin
|
and a range query.
|
||||||
vain silloin, kun tämä on välttämätöntä,
|
The idea is that the lazy update will be propagated
|
||||||
jotta puun käsittely on tehokasta.
|
downwards only when it is necessary,
|
||||||
|
so that the operations are always efficient.
|
||||||
|
|
||||||
Seuraava kuva näyttää, kuinka äskeinen puu muuttuu,
|
The following picture shows how the tree changes
|
||||||
kun siitä lasketaan puun alle merkityn välin summa:
|
when we calculate the sum in the marked range:
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=0.7]
|
\begin{tikzpicture}[scale=0.7]
|
||||||
\draw (0,0) grid (16,1);
|
\draw (0,0) grid (16,1);
|
||||||
|
@ -495,52 +488,54 @@ kun siitä lasketaan puun alle merkityn välin summa:
|
||||||
\draw[color=blue,thick] (8,1.5) rectangle (12,5.5);
|
\draw[color=blue,thick] (8,1.5) rectangle (12,5.5);
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
Tämän kyselyn seurauksena laiska muutos eteni alaspäin
|
The result of this query was that a lazy update was
|
||||||
laatikolla ympäröidyssä puun osassa.
|
propagated downwards in the nodes that are inside the rectangle.
|
||||||
Laiskaa muutosta täytyi viedä alaspäin, koska kyselyn
|
It was necessary to propagate the lazy update,
|
||||||
kohteena oleva väli osui osittain laiskan muutoksen välille.
|
because some of the updated elements were inside the range.
|
||||||
|
|
||||||
Huomaa, että joskus puussa olevia laiskoja muutoksia täytyy yhdistää.
|
Note that sometimes it's necessary to combine lazy updates.
|
||||||
Näin tapahtuu silloin, kun solmussa on valmiina laiska muutos
|
This happens when a node already has a lazy update,
|
||||||
ja siihen tulee ylhäältä toinen laiska muutos.
|
and another lazy update will be added to it.
|
||||||
Tässä tapauksessa yhdistäminen on helppoa,
|
In the above tree, it's easy to combine lazy updates
|
||||||
koska muutokset $z_1$ ja $z_2$ aiheuttavat yhdessä muutoksen $z_1+z_2$.
|
because updates $z_1$ and $z_2$ combined equal to update $z_1+z_2$.
|
||||||
|
|
||||||
\subsubsection{Polynomimuutos}
|
\subsubsection{Polynomial update}
|
||||||
|
|
||||||
Laiskaa segmenttipuuta voi yleistää niin,
|
A lazy update can be generalized so that it's
|
||||||
että väliä muuttaa polynomi
|
allowed to update a range by a polynomial
|
||||||
\[p(u) = t_k u^k + t_{k-1} u^{k-1} + \cdots + t_0.\]
|
\[p(u) = t_k u^k + t_{k-1} u^{k-1} + \cdots + t_0.\]
|
||||||
|
|
||||||
Ideana on, että välin ensimmäisen kohdan
|
Here, the update for the first element in the range is $p(0)$,
|
||||||
muutos on $p(0)$, toisen kohdan muutos on $p(1)$ jne.,
|
for the second element $p(1)$, etc., so the update
|
||||||
eli välin $[a,b]$ kohdan $i$ muutos on $p(i-a)$.
|
at index $i$ in range $[a,b]$ is $p(i-a)$.
|
||||||
Esimerkiksi polynomin $p(u)=u+1$ lisäys välille
|
For example, adding a polynomial $p(u)=u+1$
|
||||||
$[a,b]$ tarkoittaa, että kohta $a$ kasvaa 1:llä,
|
to range $[a,b]$ means that the element at index $a$
|
||||||
kohta $a+1$ kasvaa 2:lla, kohta $a+2$ kasvaa 3:lla jne.
|
increases by 1, the element at index $a+1$
|
||||||
|
increases by 2, etc.
|
||||||
|
|
||||||
Polynomimuutoksen voi toteuttaa niin,
|
A polynomial update can be supported by
|
||||||
että jokaisessa solmussa on $k+2$ arvoa,
|
storing $k+2$ values to each node where $k$
|
||||||
missä $k$ on polynomin asteluku.
|
equals the degree of the polynomial.
|
||||||
Arvo $s$ kertoo solmua vastaavan välin summan kuten ennenkin,
|
The value $s$ is the sum of the elements in the range,
|
||||||
ja arvot $z_0,z_1,\ldots,z_k$ ovat polynomin kertoimet,
|
and values $z_0,z_1,\ldots,z_k$ are the coefficients
|
||||||
joka ilmaisee väliin kohdistuvan laiskan muutoksen.
|
of a polynomial that corresponds to a lazy update.
|
||||||
|
|
||||||
Nyt välin $[x,y]$ summa on
|
Now, the sum of $[x,y]$ is
|
||||||
\[s+\sum_{u=0}^{y-x} z_k u^k + z_{k-1} u^{k-1} + \cdots + z_0,\]
|
\[s+\sum_{u=0}^{y-x} z_k u^k + z_{k-1} u^{k-1} + \cdots + z_0,\]
|
||||||
jonka saa laskettua tehokkaasti osissa summakaavoilla.
|
that can be efficiently calculated using sum formulas
|
||||||
Esimerkiksi termin $z_0$ summaksi tulee
|
For example, the value $z_0$ corresponds to the sum
|
||||||
$(y-x+1)z_0$ ja termin $z_1 u$ summaksi tulee
|
$(y-x+1)z_0$, and the value $z_1 u$ corresponds to the sum
|
||||||
\[z_1(0+1+\cdots+y-x) = z_1 \frac{(y-x)(y-x+1)}{2} .\]
|
\[z_1(0+1+\cdots+y-x) = z_1 \frac{(y-x)(y-x+1)}{2} .\]
|
||||||
|
|
||||||
Kun muutos etenee alaspäin puussa,
|
When propagating an update in the tree,
|
||||||
polynomin $p(u)$ indeksointi muuttuu,
|
the indices of the polynomial $p(u)$ change,
|
||||||
koska jokaisella välillä $[x,y]$
|
because in each range $[x,y]$,
|
||||||
polynomin arvot tulevat kohdista $x=0,1,\ldots,y-x$.
|
the values are
|
||||||
Tämä ei kuitenkaan tuota ongelmia,
|
calculated for $x=0,1,\ldots,y-x$.
|
||||||
koska $p'(u)=p(u+h)$ on aina
|
However, this is not a problem, because
|
||||||
samanasteinen polynomi kuin $p(u)$.
|
$p'(u)=p(u+h)$ is a polynomial
|
||||||
Esimerkiksi jos $p(u)=t_2 u^2+t_1 u-t_0$, niin
|
of equal degree as $p(u)$.
|
||||||
|
For example, if $p(u)=t_2 u^2+t_1 u-t_0$, then
|
||||||
\[p'(u)=t_2(u+h)^2+t_1(u+h)-t_0=t_2 u^2 + (2ht_2+t_1)u+t_2h^2+t_1h-t_0.\]
|
\[p'(u)=t_2(u+h)^2+t_1(u+h)-t_0=t_2 u^2 + (2ht_2+t_1)u+t_2h^2+t_1h-t_0.\]
|
||||||
|
|
||||||
\section{Dynaaminen toteutus}
|
\section{Dynaaminen toteutus}
|
||||||
|
|
Loading…
Reference in New Issue