求组合数的三种方法
递推法+杨辉三角 ( 1 ≤ n ≤ m ≤ 2000 1 \le n \le m \le 2000 1≤n≤m≤2000)
首先这个方法的一个根本来自一个地推关系式:
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=Cm−1n+Cm−1n−1
怎么理解这个式子呢,我们可以用选择的方法来理解:
C m n C_m^n Cmn 代表从 m m m 个数中选择 n n n 个数,这一过程可以分解成两个部分
第一个数我不选,那么我将从剩下的 m − 1 m - 1 m−1 个数中选择 n n n 个数, 即 C m − 1 n C_{m - 1}^{n} Cm−1n
第一个数我选,那么我将从剩下的 m − 1 m - 1 m−1 个数中选取 n − 1 n - 1 n−1 个数, 即 C m − 1 n − 1 C_{m - 1}^{n - 1} Cm−1n−1
所以 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=Cm−1n+Cm−1n−1
所以我们可以列出一个表格,列表中的数是表中上面和表中左上的两个数 之和
同时我们还可以获得两个结论
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=Cnn−m
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 行号 |
---|---|---|---|---|---|---|---|---|
1 | 0 | |||||||
1 | 1 | 1 | ||||||
1 | 2 | 1 | 2 | |||||
1 | 3 | 3 | 1 | 3 | ||||
1 | 4 | 6 | 4 | 1 | 4 | |||
1 | 5 | 10 | 10 | 5 | 1 | 5 | ||
1 | 6 | 15 | 20 | 15 | 6 | 1 | 6 | |
1 | 7 | 21 | 35 | 35 | 21 | 7 | 1 | 7 |
代码
#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 1≤n≤m≤1e5)
首先我们知道求组合数的具体公式:
C
m
n
=
m
!
(
m
−
n
)
!
n
!
C_m^n = \frac{m!}{(m - n)! n!}
Cmn=(m−n)!n!m!
所以我们可以提前将这些数算出来,即打表
有因为涉及到除法取模, 所以我们在预处理的时候要另加处理, 我们需要知道 m ! m! m! 在 P P P 运算下的逆元
有关逆元的相关文章:
我们可以观察到:
1 n ! = 1 n ⋅ 1 ( n − 1 ) ! \frac{1}{n!} = \frac{1}{n} \cdot \frac{1}{(n - 1)!} n!1=n1⋅(n−1)!1
1 ( n − 1 ) ! \frac{1}{(n-1)!} (n−1)!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 1≤m≤n≤18)( 1 ≤ p ≤ 1 e 5 1 \le p \le 1e5 1≤p≤1e5)
挖个坑,先去补CF,要是还没补大概就是懒了