Suma u segmentu

Napisati program koji efikasno odgovara na sledeće upite nad nizom celih brojeva:

Opis ulaza

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.

Opis izlaza

Za svaki od upita druge vrste, ispisati odgovarajuću sumu.

Primer

Ulaz

8 2 5 3 2 6 4 1 7 4 s 2 4 s 5 5 u 3 10 s 2 4

Izlaz

11 4 19

Rešenje

Opis glavnog rešenja

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;
}