更好的阅读体验
\color{red}{更好的阅读体验}
更好的阅读体验
4.8 组合数
概念
- 从 n n n个不同元素中每次取出 m m m个不同元素,不管其顺序合成一组,称为从 n n n个元素中不重复地选取 m m m个元素的一个组合。
- 所有这样的组合的种数称为组合数
公式
- C n m = n ! m ! ( n − m ) ! , C n 0 = C n n = 1 C_{n}^{m}=\frac{n!}{m!(n-m)!},C_n^0=C_n^n=1 Cnm=m!(n−m)!n!,Cn0=Cnn=1
- C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnn−m
- C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1
- C n 0 + C n 1 + C n 2 + ⋯ + C n n = 2 n C_n^0+C_n^1+C_n^2+\dots+C_n^n=2^n Cn0+Cn1+Cn2+⋯+Cnn=2n
4.8.1 求组合数 I
思想
- C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1
- 递推公式求组合数
模板例题 885. 求组合数 I
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010,mod=1e9+7;
int c[N][N]; //组合数
void init(){ //预处理出全部答案
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
if(!j) c[i][j]=1; //如果j = 0,那么把c[i][j]初始化为1
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; //递推式
}
}
}
int main(){
init();
int n;
cin>>n;
while(n--){
int a,b;
cin>>a>>b;
cout<<c[a][b]<<endl;
}
return 0;
}
4.8.2 求组合数 II
思想
- C n m = n ! m ! ( n − m ) ! = n ! × ( m ! ( n − m ) ! ) − 1 = n ! × m ! − 1 × ( n − m ) ! − 1 C_{n}^{m}=\frac{n!}{m!(n-m)!}=n!\times(m!(n-m)!)^{-1}=n!\times m!^{-1}\times (n-m)!^{-1} Cnm=m!(n−m)!n!=n!×(m!(n−m)!)−1=n!×m!−1×(n−m)!−1
- 预处理
fact[N]
和infact[N]
,fact[i]
存储 i ! i! i!,infact[i]
存储 i ! − 1 i!^{-1} i!−1 - 则
C
n
m
=
C_n^m=
Cnm=
fact[n]*infact[m]*infact[n-m]
- 预处理时利用快速幂求逆元 ( m o d 1 e 9 + 7 ) \pmod {1e9+7} (mod1e9+7)
模板例题 886. 求组合数 II
描述
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+3,mod=1e9+7;
LL fact[N],infact[N];
LL qmi(LL a,LL k,LL p){
int res=1;
while(k){
if(k&1) res=res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
int main(){
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=fact[i-1]*i%mod; //存储i!
infact[i]=infact[i-1]*qmi(i,mod-2,mod)%mod; //qmi(i,mod-2,mod)快速幂求逆元
}
LL n;
cin>>n;
while(n--){
LL a,b;
cin>>a>>b;
cout<<fact[a]*infact[b]%mod*infact[a-b]%mod<<endl; //计算公式
}
return 0;
}
4.8.3 求组合数 III
思想
-
卢卡斯定理: C n m ≡ C n p m p × C n m o d p m m o d p ( m o d p ) C_n^m\equiv C_{\frac{n}{p}}^{\frac{m}p}\times C_{n~mod~p}^{m~mod~p}\pmod p Cnm≡Cpnpm×Cn mod pm mod p(modp)
-
证明 : n = n 0 p 0 + n 1 p 1 + ⋯ + n k − 1 p k − 1 + n k p k m = m 0 p 0 + m 1 p 1 + ⋯ + m k − 1 p k − 1 + m k p k 则有: ( 1 + x ) n = ( 1 + x ) n 0 p 0 + n 1 p 1 + ⋯ + n k − 1 p k − 1 + n k p k = ( 1 + x ) n 0 p 0 × ( 1 + x ) n 1 p 1 × ⋯ × ( 1 + x ) n k − 1 p k − 1 × ( 1 + x ) n k p k = ( 1 + x p 0 ) n 0 × ( 1 + x p 1 ) n 1 × ⋯ × ( 1 + x p k − 1 ) n k − 1 × ( 1 + x p k ) n k ( m o d p ) ∵ C n m 在其等式左边即为 x m 的系数,右边也为 x m 的系数 又 ∵ m 的 p 进制 : x m = x m 0 p 0 + m 1 p 1 + ⋯ + m k − 1 p k − 1 + m k p k ∴ x m k p k 项在 ( 1 + x p k ) n k 中是 C n k m k x m k p k , x m k − 1 p k − 1 项在 ( 1 + x p k − 1 ) n k − 1 中是 C n k − 1 m k − 1 x m k − 1 p k − 1 同理 : 可得 C n m = C n 0 m 0 × C n 1 m 1 × ⋯ × C n k − 1 m k − 1 × C n k m k ( m o d p ) ∵ n , m 是 p 进制数 ∴ n 0 = n m o d p , m 0 = m m o d p 此时使得 n , m 在 p 进制下右移一位,即 ⌊ n p ⌋ , ⌊ m p ⌋ 对于 ⌊ n p ⌋ , ⌊ m p ⌋ 重复上述步骤 : C ⌊ n p ⌋ ⌊ m p ⌋ ≡ C n 1 m 1 × C n 2 m 2 × ⋯ × C n k − 1 m k − 1 × C n k m k ( m o d p ) 最终得到 : C n m = C ⌊ n p ⌋ ⌊ m p ⌋ × C n m o d p m m o d p ( m o d p ) \begin{aligned} 证明:\\ &n=n_0p^0+n_1p^1+\dots+n_{k-1}p^{k-1}+n_kp^k\\ &m=m_0p^0+m_1p^1+\dots+m_{k-1}p^{k-1}+m_kp^k\\ \\ 则有:\\& \begin{aligned} (1+x)^n&=(1+x)^{n_0p^0+n_1p^1+\dots+n_{k-1}p^{k-1}+n_kp^k}\\ &=(1+x)^{n_0p^0}\times(1+x)^{n_1p^1}\times\dots\times(1+x)^{n_{k-1}p^{k-1}}\times(1+x)^{n_kp^k}\\ &=(1+x^{p^0})^{n_0}\times(1+x^{p^1})^{n_1}\times\dots\times(1+x^{p^{k-1}})^{n_{k-1}}\times(1+x^{p^k})^{n_k}\pmod p \end{aligned}\\ \\ &\because C_n^m在其等式左边即为x^m的系数,右边也为x^m的系数\\ 又&\because m的p进制:x^m=x^{m_0p^0+m_1p^1+\dots+m_{k-1}p^{k-1}+m_kp^k}\\ &\therefore x^{m_kp^k}项在(1+x^{p^k})^{n_k}中是C_{n_k}^{m_k}x^{m_kp^k},\\&x^{m_{k-1}p^{k-1}}项在(1+x^{p^{k-1}})^{n_{k-1}}中是C_{n_{k-1}}^{m_{k-1}}x^{m_{k-1}p^{k-1}}\\ &同理:可得C_n^m=C_{n_0}^{m_0}\times C_{n_1}^{m_1}\times\dots\times C_{n_{k-1}}^{m_{k-1}}\times C_{n_k}^{m_k}\pmod p\\ \\ &\because n,m是p进制数\\ &\therefore n_0=n~mod~p,m_0=m~mod~p\\ &此时使得n,m在p进制下右移一位,即\lfloor \frac{n}{p}\rfloor,\lfloor \frac{m}{p}\rfloor\\ &对于\lfloor \frac{n}{p}\rfloor,\lfloor \frac{m}{p}\rfloor 重复上述步骤:\\ &C_{\lfloor \frac{n}{p}\rfloor}^{\lfloor \frac{m}{p}\rfloor}\equiv C_{n_1}^{m_1}\times C_{n_2}^{m_2}\times\dots\times C_{n_{k-1}}^{m_{k-1}}\times C_{n_k}^{m_k}\pmod p\\\\ &最终得到:C_n^m=C_{\lfloor \frac{n}{p}\rfloor}^{\lfloor \frac{m}{p}\rfloor}\times C_{n~mod~p}^{m~mod~p}\pmod p \end{aligned} 证明:则有:又n=n0p0+n1p1+⋯+nk−1pk−1+nkpkm=m0p0+m1p1+⋯+mk−1pk−1+mkpk(1+x)n=(1+x)n0p0+n1p1+⋯+nk−1pk−1+nkpk=(1+x)n0p0×(1+x)n1p1×⋯×(1+x)nk−1pk−1×(1+x)nkpk=(1+xp0)n0×(1+xp1)n1×⋯×(1+xpk−1)nk−1×(1+xpk)nk(modp)∵Cnm在其等式左边即为xm的系数,右边也为xm的系数∵m的p进制:xm=xm0p0+m1p1+⋯+mk−1pk−1+mkpk∴xmkpk项在(1+xpk)nk中是Cnkmkxmkpk,xmk−1pk−1项在(1+xpk−1)nk−1中是Cnk−1mk−1xmk−1pk−1同理:可得Cnm=Cn0m0×Cn1m1×⋯×Cnk−1mk−1×Cnkmk(modp)∵n,m是p进制数∴n0=n mod p,m0=m mod p此时使得n,m在p进制下右移一位,即⌊pn⌋,⌊pm⌋对于⌊pn⌋,⌊pm⌋重复上述步骤:C⌊pn⌋⌊pm⌋≡Cn1m1×Cn2m2×⋯×Cnk−1mk−1×Cnkmk(modp)最终得到:Cnm=C⌊pn⌋⌊pm⌋×Cn mod pm mod p(modp)
模板例题 887.求组合数 III
描述
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 Cbamodp 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤20,
1≤b≤a≤1018,
1≤p≤105,
输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL qmi(LL a,LL k,LL p){
LL res=1;
while(k){
if(k&1) res=res*a%p;
a=a*a%p;
k>>=1;
}
return res;
}
LL C(LL a,LL b,LL p){
if(b>a) return 0;
LL res=1;
for(LL i=1,j=a;i<=b;i++,j--){
res=res*j%p;
res=res*qmi(i,p-2,p)%p;
}
return res;
}
LL lucas(LL a,LL b,LL p){
if(a<p&&b<p) return C(a,b,p);
return C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
int main(){
int n;
cin>>n;
while(n--){
LL a,b;
LL p;
cin>>a>>b>>p;
cout<<lucas(a,b,p)<<endl;
}
return 0;
}
4.8.4 求组合数 IV
思想
- 利用高精度存储所有的方案数
模板例题 888. 求组合数 IV
描述
输入 a,b,求 Cba 的值。
注意结果可能很大,需要使用高精度计算。
输入格式
共一行,包含两个整数 a 和 b。
输出格式
共一行,输出 Cba 的值。
数据范围
1≤b≤a≤5000
5 3
输出样例:
输出样例:
10
代码
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int primes[N];
int sum[N];
bool vis[N];
int cnt;
void get_primes(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){
vis[primes[j]*i]=1;
if(i%primes[j]==0) break;
}
}
}
int get(int n,int p){
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
vector<int> mul(vector<int> a,int b){
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++){
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(t){
c.push_back(t%10);
t/=10;
}
return c;
}
int main(){
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++){
int p=primes[i];
sum[i]=get(a,p)-get(a-b,p)-get(b,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++){
for(int j=0;j<sum[i];j++){
res=mul(res,primes[i]);
}
}
for(int i=res.size()-1;i>=0;i--) cout<<res[i];
return 0;
}