Aritmetica modulară
Aritmetica modulară e aritmetica pe resturi.
Când spui “a mod m” te referi la restul împărțirii lui
a la m.
Apare peste tot în programare competitivă: când rezultatul
poate fi foarte mare (10^18 sau mai mare), cerința e de obicei
să-l dai modulo un număr prim - de exemplu
10^9 + 7.
Operatorul % în C++
int a = 17;
int b = 5;
cout << a % b; // 2 (17 = 3*5 + 2)Reguli:
a % breturnează restul împărțiriia % bare semnul luia(în C++)a % b = 0⟺bdividea
Proprietăți fundamentale
Pentru orice a, b,
m:
(a + b) mod m = ((a mod m) + (b mod m)) mod m
(a - b) mod m = ((a mod m) - (b mod m) + m) mod m
(a * b) mod m = ((a mod m) * (b mod m)) mod m
De ce e util? Dacă a și b sunt mari
(risc de overflow), le putem reduce modulo
m pe parcurs.
Exemplu
Vrem (12345678 * 98765432) mod 1000.
Cu overflow: 12345678 * 98765432 = ... depășește
int ușor.
Cu mod pe parcurs:
12345678 mod 1000 = 678
98765432 mod 1000 = 432
(678 * 432) mod 1000 = 293056 mod 1000 = 56
Overflow și
long long
În C++, int acoperă doar până la ~2*10^9. Pentru
înmulțiri e risc de overflow.
int a = 1000000;
int b = 1000000;
int c = a * b; // OVERFLOW - c devine un număr negativ aiurea
long long c2 = a * b; // TOT OVERFLOW - multiplicația e in int inainte de atribuire!
long long c3 = (long long)a * b; // CORECT - cast la long long inainteRegulă de siguranță: pentru mod, folosește
long long pentru toate variabilele
implicate în calcul.
Scăderea modulară
În C++, a - b poate fi negativ.
Atunci (a - b) % m dă un rezultat negativ (în
C++).
long long rezultat = (a - b) % MOD; // poate fi negativ!
// CORECT:
long long rezultat = ((a - b) % MOD + MOD) % MOD;Adăugarea unui MOD garantează că rezultatul e
pozitiv.
Dacă a - b poate fi și mai negativ (de exemplu
din mai multe scăderi), adaugă mai mult:
long long rezultat = ((a - b) % MOD + 2*MOD) % MOD;Codul tipic
const long long MOD = 1000000007; // 10^9 + 7
long long add(long long a, long long b) {
return (a + b) % MOD;
}
long long sub(long long a, long long b) {
return ((a - b) % MOD + MOD) % MOD;
}
long long mul(long long a, long long b) {
return (a * b) % MOD;
}Aceste funcții pot fi folosite peste tot unde faci operații modulo.
De ce 10^9 + 7?
E un număr prim suficient de mare, iar
(10^9 + 7)^2 < 2^63 - deci
long long * long long mod 10^9+7 nu depășește
long long (max ~9.2 * 10^18).
Alte alegeri populare:
998244353(prim, util pentru FFT modular)10^9 + 9
Împărțirea modulară și inversul modular
Aritmetica modulară cu +, -,
* e simplă. Dar împărțirea e mai
delicată.
(a / b) mod m ≠ ((a mod m) / (b mod m)) mod m în general!
În schimb, folosim inversul modular: cel mai
mic b^(-1) pentru care
b * b^(-1) ≡ 1 (mod m).
Atunci:
a / b ≡ a * b^(-1) (mod m)
Calculul inversului modular
Dacă m e prim și
gcd(b, m) = 1, prin mica teoremă a lui
Fermat:
b^(m-1) ≡ 1 (mod m)⟹b^(-1) ≡ b^(m-2) (mod m)
Calculăm b^(m-2) cu exponentiere
rapidă în O(log m).
long long putereModulo(long long baza, long long exp, long long m) {
long long rez = 1;
baza %= m;
while (exp > 0) {
if (exp & 1) rez = rez * baza % m;
baza = baza * baza % m;
exp >>= 1;
}
return rez;
}
long long invers(long long a) {
return putereModulo(a, MOD - 2, MOD);
}
long long div_mod(long long a, long long b) {
return a * invers(b) % MOD;
}Atenție: inversul modular există
doar dacă gcd(b, m) = 1. Dacă
m e prim, asta e garantat pentru orice
b care nu e multiplu de m.
Problemă clasică: număr de combinări modulo
Combinări C(n, k) = n! / (k! * (n-k)!). Pentru
n = 10^5, C(n, k) e imens.
Precalculăm factorialele și inverselele lor modulo:
const int MAXN = 100001;
long long fact[MAXN], invFact[MAXN];
void precalculare() {
fact[0] = 1;
for (int i = 1; i < MAXN; i++)
fact[i] = fact[i-1] * i % MOD;
invFact[MAXN-1] = putereModulo(fact[MAXN-1], MOD-2, MOD);
for (int i = MAXN-2; i >= 0; i--)
invFact[i] = invFact[i+1] * (i+1) % MOD;
}
long long combinari(int n, int k) {
if (k < 0 || k > n) return 0;
return fact[n] * invFact[k] % MOD * invFact[n-k] % MOD;
}Precalculare: O(n). Interogare C(n, k): O(1).
Cod complet: exemplu
Calculăm (a + b) și (a * b) modulo
10^9 + 7.
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
const long long MOD = 1000000007;
int main()
{
long long a, b;
fin >> a >> b;
fout << (a + b) % MOD << "\n";
fout << ((a - b) % MOD + MOD) % MOD << "\n";
fout << (a * b) % MOD << "\n";
return 0;
}date.in:
999999999999 999999999998date.out:
999999985
1
48Pas cu pas: calculăm C(5, 2) mod 7
C(5, 2) = 5! / (2! * 3!) = 120 / 12 = 10, iar
10 mod 7 = 3.
Precalculare fact (mod 7)
- fact[0] = 1
- fact[1] = 1
- fact[2] = 2
- fact[3] = 6
- fact[4] = 24 mod 7 = 3
- fact[5] = 5*3 mod 7 = 15 mod 7 = 1
Inversele
- Fermat:
b^(-1) ≡ b^(7-2) = b^5 (mod 7) invFact[5] = 1^5 = 1invFact[4] = invFact[5] * 5 = 5invFact[3] = invFact[4] * 4 = 20 mod 7 = 6invFact[2] = invFact[3] * 3 = 18 mod 7 = 4invFact[1] = invFact[2] * 2 = 8 mod 7 = 1invFact[0] = invFact[1] * 1 = 1
Calcul C(5, 2)
fact[5] * invFact[2] * invFact[3] = 1 * 4 * 6 = 24 mod 7 = 3
✓
Proprietăți geniale ale mod
1. Congruența
a ≡ b (mod m) înseamnă
a mod m = b mod m (sau echivalent,
m | (a - b)).
17 ≡ 5 (mod 6)
24 ≡ 0 (mod 8)
2. Operațiile se transmit
Dacă a ≡ a' (mod m) și
b ≡ b' (mod m):
a + b ≡ a' + b' (mod m)a * b ≡ a' * b' (mod m)
Asta legitimează tot ce am spus.
3. mod nu comută cu /
sau ^
(a^b) mod m ≠ (a mod m)^(b mod m) mod m- Pentru puteri, reduce doar baza:
(a mod m)^b mod m
Greșeli frecvente
1. Overflow la înmulțire
long long a = 1e9, b = 1e9;
long long c = a * b % MOD; // CORECT daca a, b < MOD
long long c = a + b; // OK
int x = 1e9, y = 1e9;
long long c = x * y; // OVERFLOW! x * y se face in int
long long c = (long long)x * y; // CORECT2. Scădere negativă
long long diff = (a - b) % MOD; // NEGATIV in C++!
// CORECT:
long long diff = ((a - b) % MOD + MOD) % MOD;3. Inversul modular
pentru m nu prim
Dacă m nu e prim, nu poți folosi Fermat.
Folosește algoritmul lui Euclid extins pentru a
calcula inversul.
4. Inversul lui 0
0^(-1) mod m nu există. Nu
încerca să împarți la 0 modular.
5. Pentru operații simple, folosește funcții
Evită să scrii (... + ... * ... + MOD) % MOD
prin cod. Folosește funcții add, mul,
sub - mai puține bug-uri.
Ce să reții
- Aritmetica modulară = aritmetica pe resturi, apare peste tot în probleme cu numere mari.
- Operațiile
+,-,*se distribuie prinmod:(a op b) mod m = ((a mod m) op (b mod m)) mod m. - Scăderea: adaugă
MODînainte de% MODpentru a evita rezultate negative. - Folosește
long longpentru a evita overflow. - Împărțirea necesită invers
modular:
a/b ≡ a * b^(-1) (mod m), iarb^(-1) = b^(m-2) mod mdacăme prim. - Modulul standard:
10^9 + 7(prim, evită overflow lalong long). - Precalculează factoriale și inverse pentru combinări rapide în O(1).