Python快速求組合數C(n,m)三種方法整理

百度百科對於組合數的定義是:從n個不同元素中,任取m(m≤n)個元素併成一組,叫做從n個不同元素中取出m個元素的一個組合;從n個不同元素中取出m(m≤n)個元素的所有組合的個數,叫做從n個不同元素中取出m個元素的組合數。
由於經常遇到一些組合數問題,所以整理一些常見的快速求組合數的方法,附上Python的實現代碼。

一、m,n不是特別大的時候:

(nm)=n!m!(nm)!\binom {n}{m}=\frac {n!}{m!*(n-m)!}
可以直接調用math.factorial求得階乘,然後算出組合數,如下:

import math
n,m = map(int,input().split())
print(math.factorial(n)//(math.factorial(m)*math.factorial(n-m)))

輸入:

5 3

輸出:

10

二、用定義式遞歸:

(nm)=(n1m1)+(n1m)\binom {n}{m}=\binom {n-1}{m-1}+\binom {n-1}{m}
遞歸出口就在於當n=m或者m=1的時候。

n,m = map(int,input().split())
def rec(n,m):
    if m == n:
        return 1
    elif m == 1:
        return n
    else:
        return rec(n-1,m-1)+rec(n-1,m)
    
print(rec(n,m))

輸入:

10 3

輸出:

120

三、逆元+快速冪思想參考大佬

前面的兩種方法,在n,m數字很大的時候,運行時間會很長。在介紹第三個方法之前,先來介紹幾個概念,不當之處,歡迎指點。

(1)、同餘定理

百度百科:同餘定理數論中的重要概念。給定一個正整數m,如果兩個整數a和b滿足a-b能夠被m整除,即(a-b)/m得到一個整數,那麼就稱整數a與b對模m同餘,記作a≡b(mod m)。對模m同餘是整數的一個等價關係。

53(mod 2) #5和 3對模2同餘

(2)、模的加減乘除運算

取模運算的等價變形適合加法、減法、乘法
(a+b)%p=(a%p+b%p)%p(a + b) \% p = (a \% p + b \% p) \% p
(ab)%p=(a%pb%p)%p(a - b)\% p = (a\% p - b\% p) \% p
(ab)%p=(a%pb%p)%p( a * b) \% p = (a \% p * b \% p) \% p

但是,取模運算的等價變形不符合除法
a/b%p(a%p/b%p)%pa/b \% p ≠ (a\%p / b\%p) \% p

比如:
100%20%11=5%11=5(100 \% 20)\% 11 = 5 \% 11 = 5
100%11/20%11)%11=(1/9)%11=0%11=0(100\%11 / 20\%11) \% 11 = (1 / 9) \% 11 = 0 \% 11 = 0

(3)、逆元

逆元:對於a和p,若a和p互素且
(ab)%p1 (a * b) \% p ≡ 1
則稱b爲a%p的逆元。

假設c爲b%p的逆元,即bc%p=1b*c\%p=1

(a/b)%p(a / b) \% p
=(a/b)1%p= (a / b) * 1 \% p
=(a/b)(bc%p)%p=(a/b)*(b*c\%p)\%p
=ac%p=a*c\%p
=(a%p)(c%p)%p=(a\%p)*(c\%p)\%p

這樣就把求(a/b)%p(a / b) \% p轉化成一個(a%p)(c%p)%p(a\%p)*(c\%p)\%p乘法問題。

(4)、費馬小定理

百度百科:費馬小定理(Fermat’s little theorem)是數論中的一個重要定理,在1636年提出。如果p是一個質數,而整數a不是p的倍數,則有a(p1)%p1a ^ {(p-1)} \% p ≡ 1

ap1=ap2aa^{(p-1)}=a^{(p-2)} * a
所以有ap2a%p1a^{(p-2)} * a\%p≡1
所以ap2%pa^{(p-2)}\%paa的逆元。

由於m,n很大,現在要求的是(nm)%p\binom{n}{m}\%p,假設pp取100000007,由於取模運算的等價變形不適用於除法,即:
(nm)%pn!%pm!%p(nm)!%p%p\binom{n}{m}\%p≠ \frac{n!\%p}{m!\%p*(n-m)!\%p}\%p
根據上面求得:

(a/b)%p=(a%p)(c%p)%p(a / b) \% p=(a\%p)*(c\%p)\%p
cb%pc是b\%p的逆元。
就相當於我們要求出m!(nm)!{m!和(n-m)!}的逆元,根據ap2%pa^{(p-2)}\%paa的逆元,得到
m!m!(p2)m!的逆元是m!^{(p-2)}
(nm)!(nm)!(p2)(n-m)!的逆元是(n-m)!^{(p-2)}
所以(nm)%p=[n!%p(m!(p2)%p)((nm)!(p2)%p)]%p\binom{n}{m}\%p=[n!\%p*(m!^{(p-2)}\%p)*((n-m)!^{(p-2)}\%p)]\%p
下面用代碼來實現:

import math
n,m = map(int,input().split())
p = 100000007
def power(x,y):     #求x的y次方
    p = 100000007
    res = 1
    while y:
        if y % 2 != 0:
            res *= (x%p)
        y >>= 1
        x *= (x%p)
    return res
a = (math.factorial(n))%p
b = (power(math.factorial(m),(p-2)))%p
c = (power(math.factorial(n-m),(p-2)))%p
print(a*b*c%p)

中間的power函數是用快速冪做的,直接用Python的運算符“**”的話會非常大,會超時。
小白歡迎大佬指點~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章