Numere mari: înmulțirea a două numere mari
După ce am învățat cum înmulțim un număr mare cu unul mic (lecția 118), e timpul să generalizăm: înmulțirea a două numere mari, ambele reprezentate ca vectori de cifre.
E operația cea mai puternică din setul de operații pe numere mari - apare în calcule de factorial, puteri, exponențiere rapidă pentru numere mari etc.
Ideea
Ca la școala primară - înmulțim fiecare
cifră a lui a cu fiecare
cifră a lui b și adunăm rezultatele pe
poziția potrivită.
1 2 3
× 4 5
─────────
6 1 5 ← 123 × 5 = 615
+ 4 9 2 0 ← 123 × 40 = 4920 (shift 1 la stânga)
─────────
5 5 3 5 ← rezultatul final: 123 × 45 = 5535
Formula cheie
Dacă a[i] e cifra pe poziția i a
lui a și b[j] e cifra pe poziția
j a lui b, atunci produsul
a[i] * b[j] contribuie la cifra de pe
poziția i + j - 1 a rezultatului (pentru
indexare de la 1).
c[i + j - 1] += a[i] * b[j]
După toate adunările, propagăm transportul
prin întregul vector c.
Algoritmul
1. Inițializăm c[] cu 0
2. Pentru fiecare i de la 1 la lenA:
Pentru fiecare j de la 1 la lenB:
c[i + j - 1] += a[i] * b[j]
3. Propagăm transportul prin c:
Pentru k de la 1 la lenA + lenB:
transport = c[k] / 10
c[k] = c[k] % 10
c[k + 1] += transport
4. Stabilim lungimea (eliminăm zerourile de la coadă)
Pas cu pas:
123 × 45 = 5535
Reprezentare:
a = 123→ a[1]=3, a[2]=2, a[3]=1, lenA = 3b = 45→ b[1]=5, b[2]=4, lenB = 2
Etapa 1: produse fără transport
| i | j | a[i]·b[j] | poziția i+j-1 | c după |
|---|---|---|---|---|
| 1 | 1 | 3·5 = 15 | 1 | c[1] = 15 |
| 1 | 2 | 3·4 = 12 | 2 | c[2] = 12 |
| 2 | 1 | 2·5 = 10 | 2 | c[2] = 12+10 = 22 |
| 2 | 2 | 2·4 = 8 | 3 | c[3] = 8 |
| 3 | 1 | 1·5 = 5 | 3 | c[3] = 8+5 = 13 |
| 3 | 2 | 1·4 = 4 | 4 | c[4] = 4 |
După etapa 1: c = [_, 15, 22, 13, 4]
Etapa 2: propagăm transportul
| k | c[k] înainte | transport | c[k] după | c[k+1] după |
|---|---|---|---|---|
| 1 | 15 | 1 | 5 | 22+1 = 23 |
| 2 | 23 | 2 | 3 | 13+2 = 15 |
| 3 | 15 | 1 | 5 | 4+1 = 5 |
| 4 | 5 | 0 | 5 | - |
După etapa 2: c = [_, 5, 3, 5, 5]
Rezultat
c[4]=5, c[3]=5, c[2]=3, c[1]=5 → afișat de la
dreapta la stânga → 5535 ✓
Cod
const int NMAX = 2005;
int a[NMAX], b[NMAX], c[NMAX * 2];
void inmultire(int a[], int b[], int c[]) {
// Initializam c cu 0
for (int i = 0; i < NMAX * 2; i++) {
c[i] = 0;
}
// Etapa 1: produsele pe pozitiile i+j-1
for (int i = 1; i <= a[0]; i++) {
for (int j = 1; j <= b[0]; j++) {
c[i + j - 1] = c[i + j - 1] + a[i] * b[j];
}
}
// Etapa 2: propagam transportul
int len = a[0] + b[0];
for (int k = 1; k <= len; k++) {
c[k + 1] = c[k + 1] + c[k] / 10;
c[k] = c[k] % 10;
}
// Eliminam zerourile de la coada
while (len > 1 && c[len] == 0) {
len--;
}
c[0] = len;
}Complexitate: O(lenA · lenB) -
pătratic în lungime.
Pentru a și b de câte 1000 cifre,
algoritmul face 10^6 operații - rapid.
Cod complet
#include <fstream>
#include <cstring>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
const int NMAX = 2005;
int a[NMAX], b[NMAX], c[NMAX * 2];
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 inmultire(int a[], int b[], int c[]) {
for (int i = 0; i < NMAX * 2; i++) c[i] = 0;
for (int i = 1; i <= a[0]; i++)
for (int j = 1; j <= b[0]; j++)
c[i + j - 1] = c[i + j - 1] + a[i] * b[j];
int len = a[0] + b[0];
for (int k = 1; k <= len; k++) {
c[k + 1] = c[k + 1] + c[k] / 10;
c[k] = c[k] % 10;
}
while (len > 1 && c[len] == 0) len--;
c[0] = len;
}
int main() {
citire(a);
citire(b);
inmultire(a, b, c);
afisare(c);
return 0;
}date.in:
123
45date.out:
5535De ce c[i + j - 1]?
Poziția i în a reprezintă cifra de
rang 10^(i-1). Produsul a[i] · b[j]
are rangul 10^(i-1) · 10^(j-1) = 10^(i+j-2), deci
contribuie la poziția i + j - 1 a rezultatului
(indexare de la 1).
Dacă ai folosi indexare de la 0, formula ar fi
c[i + j].
Lungimea rezultatului
Produsul a două numere cu
lenArespectivlenBcifre are cel multlenA + lenBcifre (și cel puținlenA + lenB - 1).
99 · 99 = 9801→ 4 cifre (2 + 2)10 · 10 = 100→ 3 cifre (2 + 2 - 1)1 · 1 = 1→ 1 cifră (1 + 1 - 1)
De aceea alocăm c cu dimensiune
NMAX * 2.
Aliasing:
inmultire(a, a, c) funcționează?
Da - citim cifrele din a și b
înainte să scriem în c, iar
c e inițializat cu 0. Chiar dacă
a == b (vrem a²), totul merge corect
pentru că vectorii rămân neschimbați în etapa 1.
Atenție: inmultire(a, b, a)
(rezultat suprascris peste a) nu e sigur - am citi
din a după ce am modificat a[k].
Folosește un vector temporar.
Aplicație: factorial mare
Calculează
n!pentrunpână la 1000.
1000! are ~2568 cifre - nu încape în niciun tip
nativ. Folosim numere mari.
int fact[NMAX], temp[NMAX];
int main() {
int n;
fin >> n;
fact[0] = 1;
fact[1] = 1; // fact = 1
int mic[NMAX] = {0};
mic[0] = 1;
mic[1] = 0; // mic = numar mic convertit in vector
for (int i = 2; i <= n; i++) {
// Convertim i in vector (max 4 cifre pentru n <= 1000)
int x = i, len = 0;
while (x > 0) { mic[++len] = x % 10; x /= 10; }
mic[0] = len;
inmultire(fact, mic, temp);
// Copiem temp in fact
for (int k = 0; k <= temp[0]; k++) fact[k] = temp[k];
}
afisare(fact);
return 0;
}Pentru n = 20:
2432902008176640000
Pentru n = 1000 - un număr de 2568 cifre
calculat instant.
Aplicație: puterea unui număr
Calculează
a^npentruamic șinpână la 1000.
// rez = 1
rez[0] = 1; rez[1] = 1;
// mic = a
mic[0] = ...; // cifrele lui a
for (int i = 1; i <= n; i++) {
inmultire(rez, mic, temp);
// Copiem temp in rez
for (int k = 0; k <= temp[0]; k++) rez[k] = temp[k];
}Pentru 2^1000 - un număr de 302 cifre calculat
în timp liniar.
Algoritm mai rapid: Karatsuba
Pentru numere foarte mari (peste 10.000 cifre), există
algoritmul Karatsuba care rulează în
O(n^1.585) în loc de O(n²). E bazat pe
divide et impera și un truc algebric ingenious.
Pentru olimpiade de liceu, algoritmul naiv O(n²)
e aproape mereu suficient.
Truc pentru reducerea constantei: în loc să
ții câte o cifră pe poziție, poți ține grupuri de 4
cifre (baza 10000). Produsul a două grupuri (max 10^8)
încape în long long, iar algoritmul devine de ~4x
mai rapid.
Greșeli frecvente
1. Dimensiune insuficientă pentru c
int c[NMAX]; // GRESIT - rezultatul poate avea pana la 2·NMAX cifre
int c[NMAX * 2]; // CORECT2. Uitarea inițializării cu 0
// Fara "for (int i = 0; i < NMAX * 2; i++) c[i] = 0;"
// c contine gunoi din apelul precedent → rezultat gresitVectorii globali sunt inițializați cu 0 de compilator, dar
dacă apelezi inmultire de mai multe ori, trebuie
să-l resetezi.
3. Indexare greșită
c[i + j] += a[i] * b[j]; // GRESIT pentru indexare de la 1
c[i + j - 1] += a[i] * b[j]; // CORECT pentru indexare de la 14. Overflow la
c[i+j-1] += a[i] * b[j]
a[i] * b[j] e maxim 9 * 9 = 81.
Dacă lenA = lenB = 10000, c[k] se incrementează cu
81 de ~10000 ori → c[k] ≤ 810000, încape în
int.
Pentru lenA și lenB foarte mari
(peste 100.000 cifre), folosește long long pentru
c.
5. Uitarea propagării transportului
// Etapa 1: produsele
for (...) c[i+j-1] += a[i] * b[j];
// Uitat etapa 2! Rezultat gresit.După etapa 1, c[k] poate avea valori ≥ 10 -
trebuie obligatoriu să propagi transportul.
6. Nu ștergi zerourile de la coadă
// 100 * 0 = 000 in loc de 0 daca nu stergem
while (len > 1 && c[len] == 0) len--;Dacă unul dintre numere e 0 sau rezultatul are zerouri la capăt, trebuie să elimini cifrele inutile.
Ce să reții
- Înmulțirea a două numere mari: fiecare
cifră din
acu fiecare cifră dinb, adunate pe pozițiai + j - 1. - Două etape: produsele (fără transport) + propagarea transportului.
- Rezultatul are cel mult
lenA + lenBcifre - alocăccu dimensiuneNMAX * 2. - Complexitate: O(lenA · lenB) - pătratic în lungime.
- Aplicații: factorial mare, puteri mari, exponențiere rapidă pentru numere mari.
- Pentru viteză suplimentară: baza 10000 (grupuri de 4 cifre) sau algoritmul Karatsuba (O(n^1.585)).
- Elimină zerourile de la coadă la final.