Numere mari: adunarea
Adunarea a două numere mari se face cifrică, exact ca la școala primară: aduni cifrele poziție cu poziție, propagând transportul (carry) când suma depășește 9.
Lecția presupune că ai citit introducerea - știi deja cum se reprezintă
un număr mare ca vector de cifre (LSB la a[1],
lungime la a[0]).
Adunarea unui număr mare cu un număr mic
Uneori nu aduni două numere mari, ci un număr
mare (vector de cifre) cu un număr mic
(care încape în int sau long long). E
un caz mai simplu și mai rapid - nu trebuie să transformăm
numărul mic într-un vector de cifre.
Ideea
Propagăm numărul mic direct prin cifrele numărului mare, tratându-l ca transport.
Parcurgem cifrele lui a de la stânga (LSB)
și:
- Adăugăm la cifra curentă restul împărțirii transportului la 10.
- Dacă rezultatul e ≥ 10, scădem 10 și crește transportul cu 1.
- Reducem transportul prin împărțire la 10.
Continuăm până când transportul devine 0 și am depășit ultima cifră.
Cod
void adunareMic(int a[], int x) {
int i = 1;
int transport = x;
while (i <= a[0] || transport > 0) {
int suma = a[i] + transport;
a[i] = suma % 10;
transport = suma / 10;
if (i > a[0]) a[0] = i; // am extins numarul
i++;
}
}Pas cu pas:
a = 199, x = 5
a → a[0]=3, a[1]=9, a[2]=9, a[3]=1
| i | a[i] inițial | + transport | suma | a[i] nou | transport |
|---|---|---|---|---|---|
| 1 | 9 | 5 | 14 | 4 | 1 |
| 2 | 9 | 1 | 10 | 0 | 1 |
| 3 | 1 | 1 | 2 | 2 | 0 |
Rezultat: a = 204 ✓.
Pentru numere foarte mari adăugate
Dacă x e gigantic (ex. x = 10^12),
algoritmul de mai sus funcționează tot, dar trebuie să folosești
long long pentru transport:
void adunareMic(int a[], long long x) {
int i = 1;
long long transport = x;
while (i <= a[0] || transport > 0) {
long long suma = a[i] + transport;
a[i] = suma % 10;
transport = suma / 10;
if (i > a[0]) a[0] = i;
i++;
}
}Complexitate:
O(lenA + log x).
Util când faci operații în buclă, de exemplu
pentru k = 1..N: suma += k unde suma e număr mare
dar k e int.
Algoritm
La fiecare poziție i:
suma = a[i] + b[i] + transport
c[i] = suma % 10 // cifra rezultatului
transport = suma / 10 // propagăm mai departe
Parcurgem poziții de la 1 la max(lenA, lenB).
Dacă un număr e mai scurt, cifrele lipsă se consideră
0 (automat, dacă vectorii sunt globali).
La final, dacă mai avem transport > 0, îl
scriem ca cifră nouă:
dacă transport > 0 la final:
c[++len] = transport
Pas cu pas:
457 + 1234 = 1691
Reprezentare:
a = 457→ a[0]=3, a[1]=7, a[2]=5, a[3]=4b = 1234→ b[0]=4, b[1]=4, b[2]=3, b[3]=2, b[4]=1
Adunare cifrică:
| i | a[i] | b[i] | + transport | suma | c[i] | transport nou |
|---|---|---|---|---|---|---|
| 1 | 7 | 4 | 0 | 11 | 1 | 1 |
| 2 | 5 | 3 | 1 | 9 | 9 | 0 |
| 3 | 4 | 2 | 0 | 6 | 6 | 0 |
| 4 | 0 | 1 | 0 | 1 | 1 | 0 |
Transport final = 0. Rezultat:
c[0]=4, c[1]=1, c[2]=9, c[3]=6, c[4]=1 →
1691 ✓.
Pas cu pas:
999 + 1 = 1000 (transport propagat)
a = 999→ a[0]=3, a[1]=9, a[2]=9, a[3]=9b = 1→ b[0]=1, b[1]=1
| i | a[i] | b[i] | + transport | suma | c[i] | transport nou |
|---|---|---|---|---|---|---|
| 1 | 9 | 1 | 0 | 10 | 0 | 1 |
| 2 | 9 | 0 | 1 | 10 | 0 | 1 |
| 3 | 9 | 0 | 1 | 10 | 0 | 1 |
Transport final = 1 (încă nenul). Adăugăm:
c[4] = 1, c[0] = 4.
Rezultat: c = 1000 ✓. Observă că lungimea a
crescut cu 1 față de cel mai lung număr de
intrare - asta se întâmplă când transportul „deborbează”.
Cod
const int NMAX = 1005;
int a[NMAX], b[NMAX], c[NMAX];
void adunare(int a[], int b[], int c[]) {
int len = a[0];
if (b[0] > len) {
len = b[0];
}
int transport = 0;
for (int i = 1; i <= len; i++) {
int suma = a[i] + b[i] + transport;
c[i] = suma % 10;
transport = suma / 10;
}
if (transport > 0) {
len = len + 1;
c[len] = transport;
}
c[0] = len;
}Complexitate:
O(max(lenA, lenB)).
Exemplu complet: citesc două numere și le adun
#include <fstream>
#include <cstring>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
const int NMAX = 1005;
int a[NMAX], b[NMAX], c[NMAX];
char s[NMAX];
void citire(int v[]) {
fin >> s;
int len = strlen(s);
v[0] = len;
for (int i = 1; i <= len; i++) {
v[i] = s[len - i] - '0';
}
}
void afisare(int v[]) {
for (int i = v[0]; i >= 1; i--) {
fout << v[i];
}
fout << "\n";
}
void adunare(int a[], int b[], int c[]) {
int len = a[0];
if (b[0] > len) {
len = b[0];
}
int transport = 0;
for (int i = 1; i <= len; i++) {
int suma = a[i] + b[i] + transport;
c[i] = suma % 10;
transport = suma / 10;
}
if (transport > 0) {
len = len + 1;
c[len] = transport;
}
c[0] = len;
}
int main() {
citire(a);
citire(b);
adunare(a, b, c);
afisare(c);
return 0;
}date.in:
457
1234
date.out:
1691
Aliasing:
adunare(a, b, a) e sigură?
Uneori vrei să aduni într-un vector direct (ex.
a = a + b). Funcția noastră funcționează corect în
acest caz, pentru că la pasul i citim
a[i] și b[i] înainte
să scriem c[i]. Dacă c =
a, scriem peste a[i] doar după ce l-am
folosit.
Dar atenție: aliasing-ul lui c
cu b funcționează tot pentru același motiv. Totuși,
e mai clar să păstrezi c ca vector distinct când e
posibil.
Variantă: adunarea mai multor numere
Dacă vrei să aduni N numere mari
a₁ + a₂ + ... + aₙ, aplici adunarea iterativ:
int rez[NMAX], temp[NMAX];
int N;
fin >> N;
citire(rez); // primul număr
for (int k = 2; k <= N; k++) {
citire(temp);
adunare(rez, temp, rez); // rez = rez + temp
}
afisare(rez);Aplicație tipică: calcularea sumei primelor
N factoriale 1! + 2! + ... + N!, sumei
primelor N Fibonacci, etc.
Aplicație: suma
primelor N numere
Calculează
1 + 2 + 3 + ... + NpentruN ≤ 10⁴(suma are ~8 cifre, încape înlong long, dar hai să facem cu numere mari ca exercițiu).
int suma[NMAX], unu[NMAX], curent[NMAX];
// suma = 0 (automat 0, nu trebuie setat)
suma[0] = 1; // lungime 1, cifra 0
// curent = 0 la început, apoi incrementăm
curent[0] = 1;
curent[1] = 0;
// unu = 1
unu[0] = 1;
unu[1] = 1;
int N;
fin >> N;
for (int k = 1; k <= N; k++) {
adunare(curent, unu, curent); // curent++
adunare(suma, curent, suma); // suma += curent
}
afisare(suma);Pentru N = 10: răspuns 55. Pentru
N = 1000: răspuns 500500. Pentru
N = 10⁴: răspuns 50005000. (Toate
încap în long long, dar dacă N = 10⁹,
suma depășește long long - aici ar fi util numerele
mari.)
Un exemplu care clar depășește
long long: suma a 10¹⁰ numere de
ordinul 10¹⁰ poate ajunge la 10²⁰.
Greșeli frecvente
1. Uitarea transportului final
După bucla principală, dacă transport = 1,
trebuie adăugat ca cifră nouă:
if (transport > 0) {
len = len + 1;
c[len] = transport;
}Fără asta, 999 + 1 = 000 (greșit) în loc de
1000.
2. Adunarea cu vectori locali neinițializați
Vectorii locali conțin gunoi dincolo de
len. Adunarea iese greșită. Declară
global sau folosește memset.
3.
len = max(a[0], b[0]) implementat greșit
Nu uita if (b[0] > len) len = b[0]. Dacă
folosești doar len = a[0], numerele mai lungi ca
a sunt trunchiate.
4. Transportul poate fi 0, 1, sau mai mare?
La adunare, transportul e mereu
0 sau 1 (pentru că
9 + 9 + 1 = 19, deci transport ≤ 1).
La înmulțire transportul poate fi mai mare -
vezi lecția dedicată.
5. NMAX prea mic pentru sume lungi
Dacă aduni 10⁶ numere de câte 1000 cifre, suma are încă ~1006
cifre (crește foarte puțin, proporțional cu log n).
Dar la înmulțiri sau puteri, lungimea crește mult mai rapid -
dimensionează în consecință.
Ce să reții
- Adunare cifrică cu transport:
suma = a[i] + b[i] + transport,c[i] = suma % 10,transport = suma / 10. - Parcurgi de la
1lamax(lenA, lenB). - La final, dacă
transport > 0, adaugi încă o cifră. - Cifrele lipsă (dincolo de un număr mai scurt) sunt 0 - automat, dacă vectorii sunt globali.
- Complexitate: O(max(lenA, lenB)).
- Adunarea poate să
aliaseze(c = a) fără probleme - scriemc[i]după ce citim. - Transportul maxim la adunare e 1 (spre deosebire de înmulțire).