求组合数:【四种求法 -- 逐个攻破!】

题目描述:

在这里插入图片描述
n <= 10000;

情况一:1 <= a,b <= 3000;

该数据范围不大,我们可以采用递推的方式求解组合数!时间复杂度为 O(n2) = 1e6级别左右!
组合数的本质是:从n个不同的数中选出m个的方案数是:C(n, m);
对于第一个数有选或者不选两种决策:

  1. 不选第一个数,则从剩下的n-1个数中选m个数的方案数:C(n-1,m);
  2. 选第一个数,则从剩下的n-1个数中选m-1个数的方案数:C(n-1,m-1);
  3. 所以递推式:C(n,m) = C(n-1,m) + C(n-1,m-1);
  4. 组合数的重要性质如下:
    1.C(n,0) = C(n,n) = 1;
    2.C(n, m) = C(n, n-m);
    3.C() = 递推式,,,,上面那个!
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int mod = 1e9 + 7, N = 2e3 + 10;
int t;

int C[N][N];

void init()
{
    for (int i=0; i < N; i ++)
    {
        for (int j=0; j <= i; j ++)
        {
            if (j == 0) C[i][j] = 1;
            else C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
        }
    }
}

int main()
{
    cin >> t;
    init();
    while (t --)
    {
        int n, m;
        cin >> n >> m;
        cout << C[n][m] << endl;
    }
    
    return 0;
}

情况二:1≤b≤a≤105

  1. 首先递推式会tle,那么采用公式法去直接计算!
    在这里插入图片描述
  2. 但是不难发现阶乘存在过大的情况,分子分母均是会爆int的,一方面要记得开 longlong,另一方面就是除法的处理,除法并不好处理,而相对于来说:乘法更好处理。那么如何进行转化呢?这里就需要考虑到我们的除法转乘法了!
  3. 乘法的逆元,也就是说,一个数a,模一个数p,但是余数不等于1,所以需要找一个数x,使得a*x % p == 1,则这个数x就是a的逆元,再到公式当中去,想找出逆元即找的是 1/(n-m)! 和 1/m!的逆元的值!
  4. 由于p是质数 = 1e9 + 7;且n 和 m 都小于 p,所以n,m都和p互质! (举例:p=17, n=16 =》互质,因为质数的因子只有1和它本身,而互质的话,即 gcd (p, n) = 1)
  5. 所以根据两个数(a, p)互质,想到费马定理:ap-2 % p = 1;(n-m)!的逆元就是:[(n-m)!]p-2
  6. 开两个数组:
    f[x] 存 x!%p的值!
    g[x] 存(x!)-1% p的值!
    在这里插入图片描述
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10, mod = 1e9 + 7;
typedef long long LL;
int f[N], g[N];

int quick_power(int a, int b, int p)
{
    int res=1;
    while (b)
    {
        if (b&1) res = (LL)res * a % p;
        a = (LL)a*a % p;
        b >>= 1;
    }
    return res%p;
}

void init()
{
    f[0] = g[0] = 1;
    for (int i=1; i < N; i ++)
    {
        f[i] = (LL)f[i-1] * i % mod;
        g[i] = (LL)g[i-1] * quick_power(i, mod-2, mod) % mod;
    }
}

int main()
{
    int n;
    cin >> n;
    
    init();
    while (n -- )
    {
        int a, b;
        cin >> a >> b;
        cout << (LL)f[a] * g[b] % mod * g[a-b] % mod << endl; 
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值