Napisati program koji efikasno odgovara na sledeće upite nad nizom celih brojeva:
u i x - ažuriranje elementa na indeksu i
na vrednost xs a b - računanje sume elemenata u intervalu
[a, b]Sa standardnog ulaza se unosi ceo broj n, a zatim i
n celih brojeva. Nakon toga, unosi se broj q,
a zatim i q upita u opisanom formatu.
Za svaki od upita druge vrste, ispisati odgovarajuću sumu.
8 2 5 3 2 6 4 1 7 4 s 2 4 s 5 5 u 3 10 s 2 4
11 4 19
Segmentno stablo se gradi tako da svaki čvor čuva zbir vrednosti svog
segmenta. Upit sume nad intervalom [a, b] rešava se
rekurzivno: segmenti koji su potpuno van traženog intervala ne doprinose
odgovoru, segmenti koji su potpuno unutar intervala doprinose vrednošću
upisanom u čvoru, a delimično preklapanje se rešava spuštanjem u oba
podstabla. Ažuriranje vrednosti elementa takođe se rešava rekurzivno:
najpre se spuštamo niz stablo do traženog elementa i ažuriramo njegovu
vrednost. Dok se vraćamo iz rekurzije, ažuriramo i vrednosti svih
čvorova koje smo prošli na putu do traženog elementa, pošto oni sadrže
zbir segmenta u kome se nalazi ažurirani element.
Složenost jednog upita je O(log n), dok je izgradnja
O(n).
#include <iostream>
#include <vector>
#include <cmath>
// Rekurzivna funkcija koja kreira segmentno stablo segment_tree od niza brojeva array, k predstavlja poziciju cvora unutar niza (njegov indeks), dok x i y oznacavaju krajeve intervala
// koje cvor obuhvata, na pocetku je taj interval ceo niz, a kroz rekurzvine pozive se polovi i svaki cvor pokriva manji deo pocetnog niza
void build_segment_tree(std::vector<int> &array, std::vector<int> &segment_tree, int k, int x, int y)
{
// Ukoliko smo dosli do jednoclanog niza (list u stablu), nema vise polovnjenja intervala pa zavrsavamo rekurziju
if (x == y) {
// U list stabla upisujemo odgovarajuću vrednost polaznog niza
segment_tree[k] = array[x];
return ;
}
// Trazimo sredinu intervala koji smo pokrili na visem nivou, sada imamo njegov levi i desni deo koje pokrivaju potomci cvora sa viseg nivoa. Dakle ako imamo deo niza 1, 2, 3, 4
// i roditeljski cvor C1 pokriva 1, 2, 3, 4 onda njegovi potomci C2 i C3 pokrivaju redom 1, 2 i 3, 4.
int middle = (x + y) / 2;
// Pozivamo rekurziju za kreiranje potomaka cvora (levo i desno)
// Levi potomak je na poziciji 2 * k + 1 u odnosu na poziciju oca
build_segment_tree(array, segment_tree, 2 * k + 1, x, middle);
// Desni potomak je na poziciji 2 * k + 2 u odnosu na poziciju oca
build_segment_tree(array, segment_tree, 2 * k + 2, middle + 1, y);
// Nakon sto smo kreirali oba sina, potrebno je odrediti koji broj se cuva u ocu, to je zbir brojeva potomaka (podsetiti se strukture stabla)
// Ukoliko ne bismo imali ovu liniju, samo bi listovi imali neke vrednosti, unutrasnji cvorovi ne bi bili popunjeni na odgovarajuci nacin
segment_tree[k] = segment_tree[2 * k + 1] + segment_tree[2 * k + 2];
}
// Funkcija koja u stablu segment_tree menja element na poziciji index i njegovu vrednost postavlja na new_value, k nam govori o poziciji cvora u stablu
void update_tree(std::vector<int> &segment_tree, int k, int x, int y, int index, int new_value)
{
// Ukoliko smo dosli do lista (to je bas onaj cvor koji treba azurirati), azuriramo mu vrednost i zavrsavamo rekurziju
if (x == y) {
segment_tree[k] = new_value;
return ;
}
// Delimo segment na 2 dela
int middle = (x + y) / 2;
// Usmeravamo pretragu, ne zelimo da idemo i levo i desno, vec biramo kuda cemo na osnovu toga gde nam je trazeni cvor
// Ukoliko je sredina segmenta desno od pozicije elementa u pocetnom nizu array, onda znaci da treba da idemo levo, inace idemo desno
if (index <= middle)
update_tree(segment_tree, 2 * k + 1, x, middle, index, new_value);
else
update_tree(segment_tree, 2 * k + 2, middle + 1, y, index, new_value);
// Nakon azuriranja lista trebalo bi azurirati i sve cvorove koji su na putu od korena do njega jer su se automatski i njihove vrednosti izmenile
segment_tree[k] = segment_tree[2 * k + 1] + segment_tree[2 * k + 2];
}
// Funkcija za odredjivanje sume elemenata segmenta [a, b]
int sum_of_segment(std::vector<int> &segment_tree, int x, int y, int a, int b, int k)
{
// Ukoliko nema preklapanja segmenata vracamo 0
if (x > b || y < a)
return 0;
// Ukoliko se ceo [x, y] nalazi unutar [a, b] vracamo sumu koju cuva cvor koji pokriva segment [x, y], samim tim i sumu segmenta [x, y]
if (x >= a && y <= b)
return segment_tree[k];
// Delimo segment [x, y] na 2 podsegmenta i problem resavamo rekurzivno za njih
int middle = (x + y) / 2;
return sum_of_segment(segment_tree, x, middle, a, b, 2 * k + 1) + sum_of_segment(segment_tree, middle + 1, y, a, b, 2 * k + 2);
}
int main ()
{
int n;
std::cin >> n;
std::vector<int> array(n);
for(int i = 0; i < n; i++)
std::cin >> array[i];
int q;
std::cin >> q;
int height = ceil(log2(n));
// Ukoliko indeksiranje ide od 0 imamo potrebu za jednim cvorom manje
int size = 2 * pow(2, height) - 1;
std::vector<int> segment_tree(size);
array.resize(pow(2, height));
build_segment_tree(array, segment_tree, 0, 0, pow(2, height) - 1);
while(q--){
char upit;
std::cin >> upit;
if(upit == 'u'){
int i, x;
std::cin >> i >> x;
update_tree(segment_tree, 0, 0, n-1, i, x);
} else {
int a, b;
std::cin >> a >> b;
std::cout << sum_of_segment(segment_tree, 0, n-1, a, b, 0) << std::endl;
}
}
return 0;
}