Sumy prefiksowe

Zadanie - szukanie sum na przedziałach

Podaj sumę na przedziale [a,b][a,b] ciągu SS o długości nn (n106).(n \leq 10^6).

przykład

Wolne rozwiązanie

Pierwsze rozwiązanie, na jakie możemy wpaść to przejrzenie wszystkich elementów od aa-tego do bb-tego i zsumowanie ich. Tak po prostu. Bez haczyków. Zastanówmy się, jaką to ma złożoność obliczeniową. Wykonamy bab – a operacji. W najgorszym wypadku, kiedy a=1a = 1 i b=n,b = n, wyniesie ona O(n).O(n). Co gdybyśmy chcieli zapytać się o sumę na przedziale mm (m106)(m \leqslant 10^6) razy? Uzyskalibyśmy wtedy złożoność O(nm),O(nm), co zdecydowanie nie brzmi satysfakcjonująco.

Sumy prefiksowe

Niech pref[i]pref[i] oznacza sumę na prefiksie o długości ii (inaczej ii-tym prefiksie). Chcemy obliczyć pref[i]pref[i] dla każdego naturalnego in.i \leq n. Pomogą nam w tym dwa spostrzeżenia:

1)1) pref[1]pref[1] = S1S_1

2)2) Dla i>1i > 1 zachodzi: pref[i]=pref[i1]+Sipref[i] = pref[i – 1] + S_i

sumy prefiksowe - przykład

Pozwala nam to policzyć wszystkie sumy prefiksowe w O(n).O(n).

Suma na przedziale

Zauważmy, że suma na przedziale [a,b][a,b] to nic innego jak pref[b]pref[a1]pref[b] – pref[a – 1] (przy czym zakładamy, że pref[0]=0pref[0]=0).

przykład

Oznacza to, że jeśli obliczymy wcześniej sumy prefiksowe w O(n)O(n) to będziemy mogli pytać się o sumy na przedziale w O(1).O(1). Nasz problem będziemy mogli rozwiązać w O(n+m)O(n + m) zamiast O(nm).O(nm). Jest to bardzo satysfakcjonujące rozwiązanie.

Dwuwymiarowe sumy prefiksowe

Dany mamy prostokąt podzielony na komórki, w każdej znajduje się liczba. Chcemy podać sumę liczb w podprostokątach.

prostokąt z liczbami

Do rozwiązania tego problemu możemy policzyć coś bardzo podobnego do standardowych sum prefiksowych. Jako A[i][j]A[i][j] oznaczmy sumę w prostokącie, którego lewy górny róg ma współrzędne [1,1],[1,1], a prawy dolny [i,j].[i,j]. Tak samo, jak ostatnio, skorzystamy z tych samych dwóch spostrzeżeń:

  1. A[1][1]=S(1,1)A[1][1] = S_{(1,1)}

  2. A[i][j]=S(i,j)+A[i1][j]+A[i][j1]A[i1][j1]A[i][j] = S_{(i,j)} + A[i – 1][j] + A[i][j – 1] – A[i – 1][j – 1]

drugi wzór - schemat

Zauważmy, że suma w prostokącie, którego lewy górny róg ma współrzędne [a,b],[a,b], a prawy dolny [c,d][c,d] to nic innego jak A[c][d]A[a1][d]A[c][b1]+A[a1][b1]A[c][d] – A[a – 1][d] – A[c][b – 1] + A[a -1][b – 1] (zakładamy tutaj, że A[0][i]=0A[0][i]=0 oraz A[i][0]=0A[i][0]=0 dla dowolnego ii).

liczenie sumy w dowolnym podprostokącie

Metodę sum prefiksowych możemy rozszerzyć na większą liczbę wymiarów, obliczając odpowiednie wzory podobnie do tych pokazanych powyżej. Jednak już dla n4n \geq 4 jest to dosyć trudne, przez co raczej rzadko widywane.

Iloczyny i xory prefiksowe - uogólnienie

Funkcja sumy nie jest jedyną funkcją, którą możemy liczyć na prefiksach. Możemy liczyć też chociażby funkcje min,min, maxmax czy xor.xor. Jednak by móc w ten sposób obliczać ich wartość dla dowolnych przedziałów, używana funkcja musi mieć zdefiniowaną odwrotność (np. odejmowanie dla dodawania, dzielenie dla mnożenia czy xorxor dla xorxor-a). Dlatego mając tablicę xorxor-ów prefiksowych XOR[i]XOR[i] możemy obliczyć xorxor na dowolnym przedziale [i,j]:[i,j]: będzie on równy XOR[j] xor XOR[i1],XOR[j] \ xor \ XOR[i-1], ponieważ funkcją odwrotną dla xorxor-a jest właśnie xor.xor. Nie możemy natomiast obliczyć minmin na przedziale mając tylko wartości dla prefiksów, ponieważ funkcja minmin nie ma swojej odwrotności.

Szczegół implementacyjny - indeksowanie

Z pewnych względów elementy naszej tablicy numerowaliśmy od jedynki zamiast od zera. Ten trick implementacyjny warto stosować w swoich programach. Pomyślmy, co stanie się, gdy ktoś zapyta nas o prefiks [1,i].[1,i]. Gdybyśmy indeksowali elementy od zera odpowiedź na nie byłaby równa pref[i1]pref[1],pref[i-1] – pref[-1], co spowoduje odwołanie się do elementu będącego poza tablicą. Problem nie będzie występować w przypadku numerowania ciągu od jedynki. W pref[0]pref[0] należy zapisać tzw. element neutralny wykonywanego działania (np. 0 dla dodawania, 1 dla mnożenia). Problemy wielowymiarowe są analogiczne.

Zadania