Clasa a XII-a · Matematică pentru programareRead in English

Combinări modulo un număr prim în C++

În engleză: Combinations modulo a prime

O(log n)C++1772 de linii4 exemple

Ideea

Calculăm C(n, k) mod P, cu P = 1 000 000 007 (prim) și n ≤ 10^6. Avem C(n, k) = n! / (k! * (n-k)!), dar împărțirea nu se poate face direct pe resturi. Înlocuim împărțirea cu înmulțirea cu inversul modular: după mica teoremă a lui Fermat, x^(P-1) ≡ 1 (mod P) pentru x nedivizibil cu P, deci inversul lui x este x^(P-2) mod P, calculat cu exponențiere rapidă. Precalculăm factorialele modulo P, apoi C(n, k) = n! * inv(k!) * inv((n-k)!) mod P.

Descrierea în engleză

We compute C(n, k) mod P, with P = 1,000,000,007 (prime) and n ≤ 10^6. We have C(n, k) = n! / (k! * (n-k)!), but division cannot be done directly on remainders. We replace division with multiplication by the modular inverse: by Fermat's little theorem, x^(P-1) ≡ 1 (mod P) for x not divisible by P, so the inverse of x is x^(P-2) mod P, computed with fast exponentiation. We precompute the factorials modulo P, then C(n, k) = n! * inv(k!) * inv((n-k)!) mod P.

Complexitate
O(n + log P)
Clasa
Clasa a XII-a (Grade 12)

Exemple

Exemplul 1

Intrare

5 2

Ieșire așteptată

10
Exemplul 2

Intrare

100 50

Ieșire așteptată

538992043
Exemplul 3

Intrare

1000000 500000

Ieșire așteptată

996692777
Exemplul 4

Intrare

3 7

Ieșire așteptată

0

Codul C++

Codul complet, cu comentarii în română. Îl poți modifica liber și îl poți reseta oricând.

combinari-modulo-prim.cpp
/*
 * Titlu: Combinări modulo un număr prim
 * Clasa: 12
 * Capitol: Matematică pentru programare
 * Descriere: Calculăm C(n, k) mod P, cu P = 1 000 000 007 (prim) și n ≤ 10^6.
 *            Avem C(n, k) = n! / (k! * (n-k)!), dar împărțirea nu se poate face
 *            direct pe resturi. Înlocuim împărțirea cu înmulțirea cu inversul
 *            modular: după mica teoremă a lui Fermat, x^(P-1) ≡ 1 (mod P) pentru
 *            x nedivizibil cu P, deci inversul lui x este x^(P-2) mod P, calculat
 *            cu exponențiere rapidă. Precalculăm factorialele modulo P, apoi
 *            C(n, k) = n! * inv(k!) * inv((n-k)!) mod P.
 * Complexitate: O(n + log P)
 *
 * Exemplu
 * Intrare: 5 2
 * Ieșire: 10
 *
 * Exemplu
 * Intrare: 100 50
 * Ieșire: 538992043
 *
 * Exemplu
 * Intrare: 1000000 500000
 * Ieșire: 996692777
 *
 * Exemplu
 * Intrare: 3 7
 * Ieșire: 0
 */
#include <iostream>
#include <vector>
using namespace std;

const long long P = 1000000007;

long long putere(long long baza, long long exponent)
{
    long long rezultat = 1;
    baza %= P;
    while (exponent > 0)
    {
        if (exponent % 2 == 1)
            rezultat = rezultat * baza % P;
        baza = baza * baza % P;
        exponent /= 2;
    }
    return rezultat;
}

long long invers(long long x)
{
    return putere(x, P - 2); // mica teoremă a lui Fermat
}

int main()
{
    int n, k;
    cin >> n >> k;
    if (k < 0 || k > n)
    {
        cout << 0 << '\n';
        return 0;
    }
    vector<long long> fact(n + 1);
    fact[0] = 1;
    for (int i = 1; i <= n; i++)
        fact[i] = fact[i - 1] * i % P;
    long long rez = fact[n] * invers(fact[k]) % P * invers(fact[n - k]) % P;
    cout << rez << '\n';
    return 0;
}

Rulează pe un g++ adevărat (C++17, -O2) prin Compiler Explorer. Programele trebuie să se termine repede: există o limită de timp.