美文网首页
组合数的四种求法

组合数的四种求法

作者: madao756 | 来源:发表于2020-05-07 19:33 被阅读0次

    前言:acwing 也做了这么久了,是时候开始复习了

    0X00 四种方法以及相应的代码

    方法 1 迭代法

    acwing 求组合数 I

    from sys import stdin
    
    def read(n=2):
        if n == 1:
            return int(stdin.readline())
        return map(int, stdin.readline().split())
    
    mod = 10 ** 9 + 7
    
    N = 2010
    n = read(1)
    comb = [[0] * N for _ in range(N)]
    
    for i in range(N):
        for j in range(i+1):
            if j == 0:
                comb[i][j] = 1
            else:
                comb[i][j] = (comb[i-1][j]+comb[i-1][j-1]) % mod
    
    while n > 0:
        n -= 1
        a, b = read()
        print(comb[a][b])
    

    核心思想是:C_i^{j} = C_{i-1}^{j-1} + C_{i-1}^{j}

    方法 2 简单逆元

    求组合数 II

    from sys import stdin
    
    def read(n=2):
        if n == 1:
            return int(stdin.readline())
        return map(int, stdin.readline().split())
    
    N = 100010
    fact = [0] * N
    infact = [0] * N
    
    fact[0] = infact[0] = 1
    
    mod = 10 ** 9 + 7
    
    def qmi(a, k, p):
        res = 1
        while k > 0:
            if k & 1 == 1:
                res = res * a % mod
            k >>= 1
            a = a * a % mod
        return res
    
    for i in range(1, N):
        fact[i] = fact[i-1] * i % mod
        infact[i] = infact[i-1] * qmi(i, mod-2, mod) % mod
    
    n = read(1)
    while n > 0:
        n -= 1
        a, b = read()
        print(fact[a] * infact[a - b] * infact[b] % mod)
       
    

    逆元的通俗解释是:a * b = a / b\ mod\ p, 也就是说除以一个数对 p 取余的结果,等于乘以一个数对 p 取余

    在模数 p 的情况下, b 存在逆元的充分必要条件是 p 是质数

    由于组合数可能很大,所以题目为了不超过 int 往往会让答案对一个质数取余数(比如 1e9 + 7)

    我们回到组合数的基本算法:

    C_{a}^{b} = \frac{a!}{b!(a-b)!} = fact[a] * infact[a-b] * infact[b]

    其中 fact[a] 表示 a 的阶乘,infact[b] 表示 b 的阶乘的逆元并且 fact[0] = infact[0] = 1

    方法3 lucas 算法

    求组合数 III

    from sys import stdin 
    
    def read(n = 2):
        if n == 1:
            return int(stdin.readline())
        return map(int, stdin.readline().split())
    
    def qmi(a, k, p):
        res = 1
        while k > 0:
            if k & 1:
                res = res * a % p
            k >>= 1
            a = a * a % p
        
        return res
                
        
    
    def C(a, b, p):
        if b > a: return 0
        x, y = 1, 1
        j = 1
        for i in range(a, a-b, -1):
            x = x * i % p
            y = y * j % p
            j += 1
        # print("x, y", x, y)
        return x * qmi(y, p-2, p) % p 
        
    def lucas(a, b, p):
        if a < p and b < p:
            return C(a, b, p)
        return C(a % p, b % p, p) * lucas(a // p, b // p, p) % p
    
    n = read(1)
    while n > 0:
        n -= 1
        a, b, p = read()
        print(lucas(a, b, p))
        
    
    摘自 oi wiki

    除此之外代码中有对 C_{a}^{b} 的化简

    C_{a}^{b} = \frac{a!}{(a-b)!b!} = \frac{a(a-1)...(a-b+1)}{b!}

    方法 4 分解质因数

    求组合数 IV

    N = 5010
    cnt = 0
    primes = [0] * N
    st = [True] * N
    
    def getPrimes(n):
        global cnt
        for i in range(2, n+1):
            if st[i]:
                primes[cnt] = i
                cnt += 1
            j = 0
            while primes[j] <= n // i:
                st[primes[j] * i] = False
                if not (i % primes[j]):
                    break
                j += 1
    
    def get(a, p):
        
        res = 0
        while a > 0:
            res += a // p
            a //= p
        
        return res
        
    a, b = map(int, input().split())
    getPrimes(a)
    sums = [0] * cnt
    for i in range(cnt):
        p = primes[i]
        sums[i] = get(a, p) - get(a-b, p) - get(b, p)
    
    res = 1
    for t, v in zip(sums, primes):
        res *= v ** t
    print(res)
    

    C_{a}^{b} = \frac{a!}{(a-b)!b!}

    再回到这个式子上,我们知道任意一个数字 n 都可以写成 n = p_1^{\alpha_1}p_2^{\alpha_2}...p_n^{\alpha_n} 也就是分解质因数

    所以我们只需要求出 C_a^{b} 的所有质因数以及他出现的次数就可以了

    回到一个最简单的例子 n! 假设所有 <= n 的质数存在 ps 中如何计算所有质数出现的次数呢?(每个质数出现的次数至少一次)

    \alpha = \lfloor \frac{n}{p} \rfloor + \lfloor \frac{n}{p^2} \rfloor...\lfloor \frac{n}{p^t} \rfloor

    0X01 总结

    我们来总结一下四种方法:

    • 底数比较小用第一种方法
    • 有模数底数比较大用第二种
    • 有模数,但模数比较小,底数很大用第三种
    • 无模数,底数比较大,用第四种

    相关文章

      网友评论

          本文标题:组合数的四种求法

          本文链接:https://www.haomeiwen.com/subject/snkzghtx.html