【4.8 组合数详解】

更好的阅读体验 \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!(nm)!n!,Cn0=Cnn=1
  • C n m = C n n − m C_n^m=C_n^{n-m} Cnm=Cnnm
  • 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=Cn1m+Cn1m1
  • 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=Cn1m+Cn1m1
  • 递推公式求组合数

模板例题 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!(nm)!n!=n!×(m!(nm)!)1=n!×m!1×(nm)!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 CnmCpnpm×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++nk1pk1+nkpkm=m0p0+m1p1++mk1pk1+mkpk(1+x)n=(1+x)n0p0+n1p1++nk1pk1+nkpk=(1+x)n0p0×(1+x)n1p1××(1+x)nk1pk1×(1+x)nkpk=(1+xp0)n0×(1+xp1)n1××(1+xpk1)nk1×(1+xpk)nk(modp)Cnm在其等式左边即为xm的系数,右边也为xm的系数mp进制:xm=xm0p0+m1p1++mk1pk1+mkpkxmkpk项在(1+xpk)nk中是Cnkmkxmkpk,xmk1pk1项在(1+xpk1)nk1中是Cnk1mk1xmk1pk1同理:可得Cnm=Cn0m0×Cn1m1××Cnk1mk1×Cnkmk(modp)n,mp进制数n0=n mod p,m0=m mod p此时使得n,mp进制下右移一位,即pn,pm对于pn,pm重复上述步骤:CpnpmCn1m1×Cn2m2××Cnk1mk1×Cnkmk(modp)最终得到:Cnm=Cpnpm×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;
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浪漫主义狗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值