Sume partiale. Suma elementelor dintr-o submatrice in C++
Cu ce ne ajută sumele parțiale?
Unele probleme cer aflarea sumei elementelor unei secvențe dintr-un şir dat. Metoda naivă, care face suma tuturor elementelor din secvența cerută, deobicei nu se încadrează în timp. Din fericire, putem apela la folosirea sumelor parțiale.
Dându-se un tablou de numere (vector, matrice, ...), putem afla instant, adică în complexitatea O(1)
, suma oricărei secvențe.
Aproape în fiecare an, la clasa a 9-a, apar probleme cu sume parțiale, atât la OJI, cât şi la ONI.
ATENȚIE! Formulele nu trebuie reținute, ci deduse.
Cuprinsul lecției
Sume parțiale pe vector
Pentru înțelegerea eficienței algoritmului, vom compara algoritmul "naiv" cu cel eficient.
PROBLEMĂ. Se citesc: n
, n
numere naturale şi doi indici, x
şi y
(x ≤ y
). Aflați suma elementelor care se află între indicii dați. Indicii încep de la 1
.
i | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
a[i] | 4 | 7 | 3 | 6 | 4 | 2 |
Algoritmul "naiv" (nerecomandat!)
După ce citim datele de intrare, inițializăm o variabilă sum = 0
şi parcurgem toate numerele din şir cu indicii de la x
la y
, adunând elementele pe rând. Complexitatea soluției este O(n)
. Soluția este bună pentru doar câteva calculări, dar de obicei, în probleme, se cer foarte multe calculări şi atunci soluția iese din timp.
int n, a[MAX + 1], x, y, sum = 0; //Declarăm variabilele
//Citim datele de intrare
fin>>n;
for(int i = 1; i <= n; i++)
fin>>a[i];
fin>>x>>y;
//Prelucrăm în O(n)
suma numerelor
for(int i = x; i <= y; i++)
sum += a[i];
//Afişăm
fout<<sum;
ALGORITMUL EFICIENT
Dacă vrem să aflăm suma elementelor cu indicii de la x
la y
, este acelaşi lucru ca şi când am afla suma elementelor de la 1
la y
, din care scădem suma primelor x - 1
numere:
a[3] + a[4] + a[5] = (a[1] + a[2] + a[3] + a[4] + a[5]) - (a[1] + a[2])
a[x] + a[x+1] + … + a[y] = a[1] + a[2] + … + a[y] - (a[1] + a[2] + … + a[x-1])
Dacă vrem să aflăm suma elementelor de la 1
la x
, putem afla răspunsul în timp ce citim numerele, creând un alt vector, s[]
, în care s[i]
este:
0
, pentrui = 0
a[i] + s[i - 1]
(elementul curent + suma elementelor anterioare), pentru1 ≤ i ≤ n
O(1)
suma elementelor dintre oricare indici.
i | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
a[i] | 4 | 7 | 3 | 6 | 4 | 2 |
s[i] | 4 | 11 | 14 | 20 | 24 | 26 |
a[3] + a[4] + a[5] = s[5] - s[3 - 1]
a[3] + a[4] + a[5] = s[5] - s[2]
Iată codul în C++:
int n, a[MAX + 1], s[MAX + 1], x, y, sum = 0; //Declarăm variabilele
s[0] = 0;
//Citim datele de intrare
fin>>n;
for(int i = 1; i <= n; i++) {
fin>>a[i];
s[i] = a[i] + s[i - 1];
}
fin>>x>>y;
//Afişăm
fout<<s[y] - s[x - 1];
Desigur, în unele situații, precum aceasta, nu mai avem nevoie de vectorul a
. Partea de citire devine:
//Citim datele de intrare
fin>>n;
for(int i = 1; i <= n; i++) {
fin>>s[i];
s[i] += s[i - 1];
}
fin>>x>>y;
Extinderea sumelor parțiale în 2D (pe matrice)
Algoritmul se bazează pe acelaşi principiu, doar că cu alte formule: creăm o altă matrice, s[][]
, în care s[i][j]
= suma tuturor elementelor din dreptunghiul cu colțurile (1, 1)
(i, j)
Am început indicii de la 1
pentru simplitate.
Cum formăm matricea?
Observăm că, parcurgând matricea de la stânga la dreapta şi de sus în jos (normal), pentru orice element (i, j)
, deja am parcurs şi prelucrat elementele (i - 1, j)
şi (i, j - 1)
. Astfel, putem să ne folosim de s[i - 1][j]
şi s[i][j - 1]
pentru determinarea sumei s[i][j]
. Iată o vizualizare a determinării sumei:

albastru: s[i][j], verde: s[i - 1][j], rosu: s[i][j - 1].
Observăm că dacă adunăm s[i - 1][j] + s[i][j - 1] + a[i][j]
(elementul curent), obținem suma elementelor de la (1, 1)
la (i, j)
+ suma elementelor de la (1, 1)
la (i - 1, j - 1)
(deoarece se găseşte în ambele sume adunate, vezi mai sus la vizualizare), deci scădem a doua sumă şi obținem exact ce ne-am dorit. Aşadar, formula pentru orice (i, j)
este următoarea: s[i][j] =
0
, pentrui == 0 || j == 0
a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]
Determinarea sumei oricărui dreptunghi din matrice
Fie i1, j1
indicii de stânga-sus şi i2, j2
indicii de dreapta-jos ai matricii. Atunci, practic, suma căutată este suma tuturor elementelor de la (1, 1)
la (i2, j2)
(adică s[i2][j2]
) minus ce rămâne în plus: partea din stânga (s[i2][j1 - 1]
), partea de deasupra (s[i1 - 1][j2]
). Observăm că am scăzut de două ori partea din stânga-sus, aşadar adunăm din nou s[i1 - 1][j1 - 1]
. În final, obținem:
suma(i1, j1, i2, j2) = s[i2][j2] - s[i1 - 1][j2] - s[i2][j1 - 1] + s[i1 - 1][j1 - 1];
Exemplu
Iată un exemplu de problemă rezolvată:
CERINȚĂ Dându-se două numere naturale nenule, n
şi m
(n, m ≤ 100
), precum şi o matrice de n
linii şi m
coloane, răspundeți la q
întrebări de forma "i1 j1 i2 j2
" cu semnificația "Care este suma elementelor din submatricea cu colțurile (i1, j1)
(stânga-sus), (i2, j2)
(dreapta-jos)?".
DATE DE INTRARE Pe prima linie a testului se află două numere, n
şi m
. Pe următoarele n
linii se află m
numere întregi, care încap în tipul de date int
. Pe următoarea linie se află numărul q
, iar pe următoarele q
linii se află cele 4 variabile corespunzătoare unei întrebări.
DATE DE IEŞIRE Pe prima linie, q
valori cu semnificația din cerință (răspunsul la întrebări).
Rezolvare pas cu pas.
#include <iostream>
#define MAX 100
using namespace std;
//Declarăm variabilele
int n, m, q, i1, i2, j1, j2;
long long s[MAX + 1][MAX + 1];
//Atenție, dacă toate elementele matricii sunt INT_MAX,
//sau o valoare foarte mare, atunci suma poate ieşi din
//int. Cel mai rău caz: 100 * 100 * INT_MAX.
int main() {
//Citim datele de intrare
cin>>n>>m;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin>>s[i][j];
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
cin>>q;
for(int i = 1; i <= q; i++) {
cin>>i1>>j1>>i2>>j2;
cout<<s[i2][j2] - s[i1 - 1][j2] - s[i2][j1 - 1] + s[i1 - 1][j1 - 1]<<" ";
}
return 0;
}
Toate formulele descrise aici (breviar teoretic)
Pe vector
s[i] = a[i] + s[i - 1];
suma(i, j) = s[j] - s[i - 1];
Pe matrice
s[i][j] = a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
suma(i1, j1, i2, j2) = s[i2][j2] - s[i1 - 1][j2] - s[i2][j1 - 1] + s[i1 - 1][j1 - 1];
Mai departe de atât?
Desigur, în limita memoriei, algoritmul se poate extinde şi pe mai mult de două dimensiuni. Momentan, rămâne ca voi să vă dați seama cum :)