乘法逆元及其組合數運算

同餘式:

設m是給定的一個正整數,a、b是整數,若滿足m|(a-b),則稱a與b對模m同餘,記爲a≡b(mod m),或記爲a≡b(m)。這個式子稱爲模m的同餘式。
a≡b(mod m) 等價於 a,b分別除以m,得到的餘數相同。

乘法逆元

概念:如果ax ≡ b (mod p) ,且gcd(a, p) = 1(a, p互質,是逆元存在的充要條件), 則稱a 的逆元爲 x。
逆元的含義:在模以p的條件下,一個數 a 如果有逆元 x 存在, 那麼除以 a 相當於乘以 x

求解逆元的方法(只介紹常用的有兩種,其他方法自行尋找):
1.拓展歐幾里得算法(算法證明請點擊我另外一篇博客
簡單說明一下,就是有兩個整數a, b,存在有x,y 使滿足貝祖等式 ax + by = gcd(a, b).
求得的 x 和 y(求得的其中一個很可能是負數)。
當a關於模以b的逆元存在,有gcd(a, b) = 1, 原式化簡爲: ax + by = 1,
方程兩邊同時模以b:
ax % b + by % b = 1 % b
ax % b = 1 % b
ax ≡ 1(mod b)
所以a的乘法逆元爲x, 同理b的乘法逆元爲y
因爲x或y有可能是負數,所以a的逆元爲 (x % p + p) % p, b的逆元爲 (y % p + p) % p.
代碼實現

int exgcd(int a, int b, int &x, int &y) { //返回gcd(a, b)
    if (!b) {
        x = 1;
        y = 0;
        return a;
    }
    int res = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return res;
}

int main() {
    int a, b, x, y;
    cin >> a >> b;
    cout << exgcd(a, b, x, y) == 1 ? (x % b + b) % b : 0 << endl;  //0代表逆元不存在
    return 0;
}

時間複雜度O(lg(mod)) , mod爲模的大小

2.費馬小定理
假設a是一個整數,p是一個質數,而整數a不是p的倍數,那麼gcd(a, p) = 1.
則有 a^(p-1)≡1(mod p)
所以 a * a ^(p-2) ≡ 1 (mod p)
所以a關於模p的逆元爲 x = a^(p-2) (mod p),用快速冪即可解決
代碼如下

typedef long long ll;
ll pow_mod(ll a, ll b, ll mod) { 
    ll res = 1;
    while (b) {
        if (b & 1)
            res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

ll solve(ll a, ll p) {  //費馬小定理求a關於b的逆元
    return pow_mod(a, p - 2, p);
}

時間複雜度O(lg(mod))

組合數運算

公式:C(n,r)=n!r!(nr)!C(n, r) = \frac {n!} {r! * (n - r)!}
我們的目標就是求取 C(n,r) % mod\ C(n, r)\ \%\ mod 的值

一.運用逆元或者拓展歐幾里得算法

1.求取1到n的階乘對mod取模的結果存入數組ans[]中
2.求取 C(n,r)C(n, r)時,先利用“拓展歐幾里得算法”或者“費馬小定理+快速冪”,求出ans[r]的逆元存入臨時變量 a1。
3.計算ans[] * a1 % mod 存入臨時變量a2 (a2 即爲n!r!%mod\frac {n!} {r!} \% mod),(式子不理解的話看上面逆元的含義)
4.求取ans[n-r]的逆元存入臨時變量a3
5.C(n,r)=a2a3%modC(n, r) = a2 * a3 \% mod

一開始求取ans[]的時間複雜度O(n),求m次組合數總的時間複雜度爲O(mnlg(mod)).
代碼如下:

void init(ll mod) {  //第一步
    for (int i = 1; i <= n; i++)
        ans[i] = ans[i-1] * i % mod;
}
ll pow_mod(ll a, ll b, ll mod) { 
    ll res = 1;
    while (b) {
        if (b & 1)
            res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

ll niyuan(ll a, ll b) {
    return pow_mod(a, b - 2, b);  
}
ll C(ll a, ll b, ll mod) {  //計算C(a, b) % mod
    return ans[a] * niyuan(ans[b], mod) % mod
     * niyuan(ans[a-b], mod) % mod;
}

二,運用盧卡斯定理(Lucas)進行求解
證明的話自行百度吧。。自己會用就好了。。
核心內容就是一條式子: C(n,m)%p=(C(n/p,m/p)%p)(C(n%p,m%p)%p)%pC(n, m) \% p = (C(n/p, m/p) \% p) * (C(n\%p, m\%p) \% p) \% p
其中n,m很大,而p相對而言很小。
代碼如下:

ll Lucas(ll n, ll m, ll mod) {
    return m ? Lucas(n / mod, m / mod, mod) * C(n % mod, m % mod, mod) % mod : 1;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章