快速求组合数的三种方法

求组合数的三种方法

递推法+杨辉三角 ( 1 ≤ n ≤ m ≤ 2000 1 \le n \le m \le 2000 1nm2000)

首先这个方法的一个根本来自一个地推关系式:
C m n = C m − 1 n + C m − 1 n − 1 C_m^n = C_{m-1}^{n} + C_{m-1}^{n - 1} Cmn=Cm1n+Cm1n1
怎么理解这个式子呢,我们可以用选择的方法来理解:

C m n C_m^n Cmn 代表从 m m m 个数中选择 n n n 个数,这一过程可以分解成两个部分

  • 第一个数我不选,那么我将从剩下的 m − 1 m - 1 m1 个数中选择 n n n 个数, 即 C m − 1 n C_{m - 1}^{n} Cm1n

  • 第一个数我选,那么我将从剩下的 m − 1 m - 1 m1 个数中选取 n − 1 n - 1 n1 个数, 即 C m − 1 n − 1 C_{m - 1}^{n - 1} Cm1n1

所以 C m n = C m − 1 n + C m − 1 n − 1 C_m^n = C_{m-1}^{n} + C_{m-1}^{n - 1} Cmn=Cm1n+Cm1n1

所以我们可以列出一个表格,列表中的数是表中上面和表中左上的两个数 之和

同时我们还可以获得两个结论

C n 1 = C n n = 1 C_n^1 = C_n^n = 1 Cn1=Cnn=1

C n m = C n n − m C_n^m = C_n^{n -m} Cnm=Cnnm

01234567行号
10
111
1212
13313
146414
151010515
16152015616
1721353521717

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long int lli;
lli P = 998244353;
lli mod(lli n) {return n % P;}
lli Compute(lli m, lli n){
    vector<lli> dp(m + 10, 0);
    dp[0] = 1;
    for(int temp = 1 ; temp <= m ; temp++){
        for(int temp2 = temp ; temp2 >= 1 ; temp2--)
            dp[temp2] = mod(dp[temp2] + dp[temp2 - 1]);
    }
    return dp[n];
}
signed main()
{
    lli m, n; cin >> m >> n;
    cout << Compute(m, n);
}

公式+快速幂( 1 ≤ n ≤ m ≤ 1 e 5 1 \le n \le m \le 1e5 1nm1e5

首先我们知道求组合数的具体公式:
C m n = m ! ( m − n ) ! n ! C_m^n = \frac{m!}{(m - n)! n!} Cmn=(mn)!n!m!
所以我们可以提前将这些数算出来,即打表

有因为涉及到除法取模, 所以我们在预处理的时候要另加处理, 我们需要知道 m ! m! m! P P P 运算下的逆元

有关逆元的相关文章:

乘法逆元 + 模的除法_yyym__的博客-CSDN博客

我们可以观察到:

1 n ! = 1 n ⋅ 1 ( n − 1 ) ! \frac{1}{n!} = \frac{1}{n} \cdot \frac{1}{(n - 1)!} n!1=n1(n1)!1

1 ( n − 1 ) ! \frac{1}{(n-1)!} (n1)!1 之前已经算过了, 所以我们只需要求出 n n n P P P 的逆元

可以使用费马小定理

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long int lli;
lli P = 998244353;
lli mod(lli n) {return n % P;}
lli fast_pow(lli n, lli power){
    lli ans = 1LL;
    while(power){
        if(power & 1) ans = mod(ans * n);
        n = mod(n * n);
        power >>= 1;
    }
    return (ans + P) % P;
}
lli Compute(lli m, lli n){
    vector<lli> info(m + 10, 0), rev(m + 10, 0);
    info[0] = 1; rev[0] = 1;
    for(int temp = 1 ; temp <= m + 1 ; temp++){
        info[temp] = mod(info[temp - 1] * temp);
        rev[temp] = mod(rev[temp - 1] * fast_pow(temp, P - 2));
    }
    return info[m] * rev[n] % P * rev[m - n] % P;
}
signed main()
{
    lli m, n; cin >> m >> n;
    cout << Compute2(m, n);
}

卢卡斯定理( 1 ≤ m ≤ n ≤ 18 1 \le m \le n \le 18 1mn18)( 1 ≤ p ≤ 1 e 5 1 \le p \le 1e5 1p1e5)

挖个坑,先去补CF,要是还没补大概就是懒了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yyym__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值