CSES - Segment Tree Beats

Segment tree beats on tekniikka, jolla voi ratkaista seuraavantyyppisiä datastruktuuriongelmia:

Kaikissa ongelmissa xix_i, 1in1 \leq i \leq n on taulukko, jonka alkuarvot on annettu, ja pitää tukea listattuja operaatiota.

Ongelma 1:

  • Annettuna l,r,vl, r, v, aseta ximax(xi,v)x_i \leftarrow \max(x_i, v) kaikille lirl \leq i \leq r
  • Annettuna l,rl, r, palauta i=lrxi\sum_{i = l}^{r} x_i

Ongelma 2:

  • Annettuna l,r,vl, r, v, aseta xivx_i \leftarrow v kaikille lirl \leq i \leq r
  • Annettuna l,r,vl, r, v, aseta xi(xi mod v)x_i \leftarrow (x_i \text{ mod } v) kaikille lirl \leq i \leq r
  • Annettuna l,rl, r, palauta i=lrxi\sum_{i = l}^{r} x_i

Ongelma 3:

  • Annettuna l,r,vl, r, v, aseta ximax(xi,v)x_i \leftarrow \max(x_i, v) kaikille lirl \leq i \leq r
  • Annettuna l,r,vl, r, v, aseta xixi+vx_i \leftarrow x_i + v
  • Annettuna l,rl, r, palauta i=lrxi\sum_{i = l}^{r} x_i

Ongelma 11 ratkeaa ajassa O(nlogn)\mathcal{O}(n \log n), ongelma 22 ajassa O(nlognlogv)\mathcal{O}(n \log n \log v), missä vv on suurin mahdollinen arvo, ja ongelma 33 ajassa O(nlog2n)\mathcal{O}(n \log^2 n).

Ideana on muokata standardikoodia laiskalle segmenttipuulle. Seuraava koodi tekee operaation välille [a,b)[a, b):

void rangeApply(int a, int b, int v, int i, int ia, int ib) {
	if (b <= ia || ib <= a) return;
	if (a <= ia && ib <= b) apply(i, v);
	else {
		int mid = (ia + ib) / 2;
		rangeApply(a, b, v, 2*i, ia, mid);
		rangeApply(a, b, v, 2*i+1, mid, ib);
		update(i);
	}
}

Haluamme korvata ehdon

b <= ia || ib <= a

voimakkaammalla ehdolla, ja ehdon

a <= ia && ib <= b

heikommalla ehdolla.

Esimerkiksi ensimmäisessä ongelmassa, korvaamme ensimmäisen ehdon ehdolla

a <= ib || b <= ia || interval_min[i] >= v

ja toisen ehdolla

a <= ia && ib <= b && interval_second_min[i] > v.

Tässä interval_second_min[i] on välin pienin arvo, joka on aidosti suurempi kuin interval_min[i].

Käytämme vastaavia ehtoja myös toisessa ongelmassa: muutamme ensimmäiseksi ehdoksi

a <= ib || b <= ia || interval_max[i] < v

ja toiseksi ehdoksi

a <= ia && ib <= b && interval_second_max[i] < v.

Ideana on se, että kun kutsumme apply-funktiota, alipuussa johon sitä kutsumme, on vain yhtä arvoa johon operaatio vaikuttaa! Jos tiedämme kuinka monta kertaa tämä arvo esiintyy alipuussa, voimme helposti päivittää välin summan.

Ratkaisun aikavaatimus voidaan todistaa potentiaalifunktiolla. Otetaan seuraava esimerkkitaulukko:

Ensimmäisessä tehtävässä käytämme potentiaalifunktiona arvoa lognc\log n \cdot c, missä cc on niiden solmujen määrä segmenttipuussa, missä solmua vastaavan välin minimi on isompi kuin solmun vanhemman välin minimi. Koska cnc \leq n, potentiaalifunktion arvo alussa on enintään O(nlogn)\mathcal{O}(n \log n), ja jokainen operaatio kasvattaa sitä enintään O(logn)\mathcal{O}(\log n).

Tässä taulukon välien minimit:

Ja solmut, jotka kontributoivat arvoon cc:

Olkoon f(x)f(x) pienin epänegatiivinen kokonaisluku, jolla x2f(x)=0\left\lfloor \frac{x}{2^{f(x)}} \right\rfloor = 0. Olkoon f(S)=xSf(x)f(S) = \sum_{x \in S} f(x) joukoilla SS. Olkoon TiT_i joukko arvoista solmun ii alipuussa, missä jokainen arvo esiintyy enintään kerran. Toisessa tehtävässä käytämme potentiaalina arvoa iI[Ti>1]f(Ti)\sum_{i} \mathbb{I}[|T_{i}| > 1] f(T_i). Potentiaalifunktion arvo alussa on enintään O(nlognlogv)\mathcal{O}(n \log n \log v), ja jokainen operaatio kasvattaa sitä enintään O(lognlogv)\mathcal{O}(\log n \log v).

Kolmannessa tehtävässä toimii sama potentiaalifunktio kuin ensimmäisessä, mutta välin kasvatus voi kasvattaa potentiaalia O(log2n)\mathcal{O}(\log^2 n).