100din100

Sume partiale. Suma elementelor dintr-o submatrice in C++

Publicat la 12 martie 2020 · algoritmi olimpiada clasa 9 cpp


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.

i123456
a[i]473642

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//pentru mai multe detalii: https://100din100.netlify.app

//Citim datele de intrare//pentru mai multe detalii: https://100din100.netlify.app
fin>>n;
for(int i = 1; i <= n; i++)
	fin>>a[i];
fin>>x>>y;

//Prelucrăm în O(n) suma numerelor//pentru mai multe detalii: https://100din100.netlify.app
for(int i = x; i <= y; i++)
	sum += a[i];

//Afişăm//pentru mai multe detalii: https://100din100.netlify.app
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, pentru i = 0
  • a[i] + s[i - 1] (elementul curent + suma elementelor anterioare), pentru 1 ≤ i ≤ n
Astfel se poate afla în O(1) suma elementelor dintre oricare indici.

i123456
a[i]473642
s[i]41114202426

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//pentru mai multe detalii: https://100din100.netlify.app
s[0] = 0;

//Citim datele de intrare//pentru mai multe detalii: https://100din100.netlify.app
fin>>n;
for(int i = 1; i <= n; i++) {
	fin>>a[i];
	s[i] = a[i] + s[i - 1];
}
fin>>x>>y;

//Afişăm//pentru mai multe detalii: https://100din100.netlify.app
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//pentru mai multe detalii: https://100din100.netlify.app
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] =

Verificare: a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] = a[i][j] + (suma(1, j, i - 1, j) + s[i - 1][j - 1]) + (suma(i, 1, i, j - 1) + s[i - 1][j - 1]) - s[i - 1][j - 1] = a[i][j] + suma(1, j, i - 1, j) + s[i][j - 1] = suma(1, j, i, j) + s[i][j - 1] = s[i][j], unde suma(i‎1, j1, i2, j2) reprezintă suma elementelor din dreptunghiul (i‎1, j1), (i2, j2)

Determinarea sumei oricărui dreptunghi din matrice

Fie i‎1, 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[i‎1 - 1][j2]). Observăm că am scăzut de două ori partea din stânga-sus, aşadar adunăm din nou s[i‎1 - 1][j1 - 1]. În final, obținem:


suma(i‎1, j1, i2, j2) = s[i2][j2] - s[i‎1 - 1][j2] - s[i2][j1 - 1] + s[i‎1 - 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 "i‎1 j1 i2 j2" cu semnificația "Care este suma elementelor din submatricea cu colțurile (i‎1, 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//pentru mai multe detalii: https://100din100.netlify.app
int n, m, q, i‎1, i2, j1, j2;
long long s[MAX + 1][MAX + 1];
//Atenție, dacă toate elementele matricii sunt INT_MAX,//pentru mai multe detalii: https://100din100.netlify.app
//sau o valoare foarte mare, atunci suma poate ieşi din//pentru mai multe detalii: https://100din100.netlify.app
//int. Cel mai rău caz: 100 * 100 * INT_MAX.//pentru mai multe detalii: https://100din100.netlify.app

int main() {
	//Citim datele de intrare//pentru mai multe detalii: https://100din100.netlify.app
	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>>i‎1>>j1>>i2>>j2;
		cout<<s[i2][j2] - s[i‎1 - 1][j2] - s[i2][j1 - 1] + s[i‎1 - 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(i‎1, j1, i2, j2) = s[i2][j2] - s[i‎1 - 1][j2] - s[i2][j1 - 1] + s[i‎1 - 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 :)

Alte resurse şi bibliografie

Iată câteva alte articole legate de sumele parțiale:

Alte articole

Primul tau program, variabile si operatii - TUTORIAL COMPLET C++

Eşti elev de liceu şi nu înțelegi informatica? Acest clip ar trebui să te ajute să înțelegi cele mai elementare aspecte privind informatica de liceu - algoritmica...

Rezolvări probleme PbInfo

Problemele de informatică pot fi dificile uneori, şi este util să vedem o soluție bună pentru a ne autoverifica. De multe ori sunt necesare doar nişte modificări minore, de aceea este bine să analizăm soluțiile bune cu atenție!

Video - Probleme Rezolvate PbInfo

Aici gasiti rezolvarea mai multor probleme de pe site-ul pbinfo.ro, în format video, pe canalul nostru de YouTube.

Ciurul lui eratostene - generarea numerelor prime - olimpiada C++

Să zicem că vrem să aflăm dacă mai multe numere sunt sau nu prime. Desigur, putem aplica algoritmul de aflare a numărului de divizori pentru toate numerele, doar că dacă avem foarte multe şi sun…

Prelucrarea cifrelor unui numar pentru incepatori in C++

Acest articol urmăreşte aplicarea diferitelor aplicații cu cifrele unui număr: suma cifrelor, numărul de cifre, oglinditul unui număr ş.a.m.d. Puteți găsi multe probleme care cer prelucrarea cif…

Verifica daca o data este valida in C++

Dându-se o dată (zi lună an), trebuie verificat dacă data este validă sau nu. Exemplu:Intrare zi = 3, luna = 4, an = 2021 Ieşire DA Pentru a verifica da…

Smenul lui Mars (Difference Arrays) in C++

Ce este Şmenul lui Mars? Să zicem că avem un şir a[] cu n elemente. Unele probleme cer efectuarea unor operații de tipul sum(st, dr, val), cu semnificația că adunăm la …

Afişare cu 2 zecimale exacte, fără şi cu rotunjire - TUTORIAL C++

Tutorialul acesta te învață cum să afişezi un număr real cu mai multe zecimale exacte. Multe probleme cer...

Algoritmi elementari - video

Tutoriale uşor de urmărit, sub forma unor videoclipuri!

Verifica daca un an este bisect in C++

Dându-se un an, trebuie verificat dacă este un an bisect sau nu. Exemplu:Intrare 2021 Ieşire NU Definiția anului bisect Deşi pare evident, anii bis…

Ce este coada/queue si cum functioneaza? Tutorial clasa a 10-a

Ce este coada? Coada (Queue din engleză) este o structură de date liniară care funcționează prin inserarea elementelor noi la spate şi prin eliminarea elementelor doar prin față. Altfel spu…

Al doilea cel mai mic/mare element al unui sir in C++

Dându-se un şir de numere naturale distincte, aflați al doilea cel mai mic număr din şir. Exemplu:Intrare 9 5 8 2 4 Ieşire 4 Explicație4 este al doilea cel mai mi…

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 n…

Primul program - Operatori si expresii - Tutorial incepatori C++

Alcătuirea unui program pentru rezolvarea unei probleme se rezumă defapt la rezolvarea unor probleme mai mici. Spre exemplu, pentru a afla suma numerelor prime dintr-un şir, trebuie să ştim cum …

Ridicarea la putere in timp logaritmic pentru olimpiada in C++

Un număr de forma b la puterea e nu poate fi calculat precum celelalte operații (adunarea, scăderea, înmulțirea, împărțirea) pur şi simplu. În articolul acesta vom prezenta un mod foarte …

Tipuri de date in C++ si C - Tutorial Incepatori

De ce avem nevoie de date? În timp ce lucrăm cu valori, avem nevoie (uneori chiar suntem siliți!) să memorăm datele undeva, în memorie. Prin intermediul variabilelor putem accesa o zonă de …

Vectori caracteristici si vectori de frecventa - Materie Olimpiada Informatica C++

Ce este un vector de frecvență sau un vector caracteristic? Vectorii caracteristici rețin dacă un element se află într-un vector sau nu. Similar, vectorii de frecvență rețin numărul de apar…

Citirea si Afisarea in fisiere in C++ - Tutorial Incepatori

De ce citirea din fişier? De multe ori este necesară citirea şi afişarea în fişier, unul din motive fiind faptul că nu trebuie să reintroducem datele de intrare de fiecare dată când rulăm p…

Creeaza un joc 2D in PYTHON pentru incepatori - Capitolul 0 (Instalare)

Care-i faza? În aceste câteva tutoriale vom învăța cum să îți creezi propriul joc video în Python. Puteți viziona atât pe YouTube, cât şi în format text pe site-ul nostru (aici adică). …

Parcurgerea matricei in spirala in C++

Cu ce ne ajută parcurgerea matricei pătratice în spirală? În concursuri şi olimpiade apar probleme legate de parcurgerea matricei în spirală. Deşi ideea de bază este destul de uşoară, algor…

Cautarea binara pentru olimpiada in C++

Ce este căutarea binară? Să zicem că ai un vector a[] cu n elemente crescătoare. Căutarea binară este un algoritm eficient care determină dacă orice număr x apare sau n…

Divizorii unui numar - tutorial incepatori in C++

Prezentul articol te va ajuta să îndeplineşti diferite aplicații cu divizorii unui număr. Trebuie cunoscută parcurgerea divizorilor unui număr, înainte de toate, ca să ne asigurăm că ştim s…

Sortarea vectorilor si a structurilor (pentru incepatori) in C++

Sortarea în informatică reprezintă procesul de a ordona un şir de obiecte (numere, caractere, sau chiar şiruri) după un anumit criteriu. Spre exemplu, putem ordona crescător sau descrescăto…

Tablelul ASCII - clasa a X-a - char C++

Ce este tabelul ASCII? Calculatorul reține numere (valori de 1 şi de 0), aşadar a fost nevoie de o corespondență între numere şi caractere pentru a putea reprezenta litere, si…

Învață să programezi gratis!

Am lansat InfoAs, o platformă de informatică cu sute de probleme, lecții și concursuri, totul gratuit. Încearcă acum!