C++求解组合数

递推法 杨辉三角

请添加图片描述
递推式Cnm=Cn−1m+Cn−1m−1C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}Cnm=Cn1m+Cn1m1
从 n个不同的数中选出 m个的方案数是 CnmC_{n}^{m}Cnm
对第 1 个数有 选或不选 两种决策: CnmC_{n}^{m}Cnm
若不选,则从剩下的 n−1个中选 m 个,即 ​; Cn−1mC_{n-1}^{m}Cn1m
若选,则从剩下的 n−1个中选 m−1 个,即​。Cn−1m−1C_{n-1}^{m-1}Cn1m1
加法原理Cnm=Cn−1m+Cn−1m−1C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}Cnm=Cn1m+Cn1m1
请添加图片描述

void comb()
{
    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;
        }
    }
}

快速幂+逆元

请添加图片描述

组合数计算优化方案

递推法 Cnm=Cn−1m+Cn−1m−1C_n^m = C_{n-1}^m + C_{n-1}^{m-1}Cnm=Cn1m+Cn1m1,会 TLE。
考虑用 Cnm=n!(n−m)!⋅m!C_n^m = \dfrac{n!}{(n - m)! \cdot m!}Cnm=(nm)!m!n! 直接计算。
开两个数组分别存模意义下的阶乘和阶乘的逆元:

  • f[x]f[x]f[x]x! mod px! \bmod px!modp 的值;
  • g[x]g[x]g[x](x!)−1 mod p(x!)^{-1} \bmod p(x!)1modp 的值。
    因为 ppp 是质数且 n,mn, mn,m 都小于 ppp,即 n,mn, mn,mppp 互质,
    所以根据费马小定理 a⋅ap−2≡1(modp)a \cdot a^{p - 2} \equiv 1 \pmod paap21(modp)
    因为a⋅a−1≡1(modp)a \cdot a^{-1} \equiv 1 \pmod paa11(modp)
    所以 a−1≡ap−2(modp)a^{-1} \equiv a^{p-2} \pmod pa1ap2(modp)
    可以用快速幂求逆元。
    无法直接计算 n!(n−m)!⋅m! mod p\dfrac{n!}{(n - m)! \cdot m!}\bmod p(nm)!m!n!modp,所以转化为 n!⋅((n−m)!⋅m!)−1 mod pn!\cdot((n-m)!\cdot m!)^{-1} \bmod pn!((nm)!m!)1modp
    所以Cnm mod p=f[n]×g[n−m]×g[m] mod pC_n^m \bmod p = f[n] \times g[n - m] \times g[m] \bmod pCnmmodp=f[n]×g[nm]×g[m]modp
    请添加图片描述
typedef long long ll;
ll qpow(ll a,int b,ll p)
{
   ll res;
   while(b)
   {
    if(a & 1)res=res*a%p;
    a=a*a%p;
    b >>= 1;
   }
   return res;
}
void init()
{
    f[0]=1,g[0]=1;
    for(int i=1;i<n;i++)
    {
        f[i] = i*f[i-1]%p;
        g[i] = g[i-1]*qpow(i,p-2)%p;
    }
}
ll comb(ll n,ll m)
{
    return f[n]*g[n-m]%p*g[m]%p;
}

卢卡斯定理

请添加图片描述
卢卡斯定理:
Cnm≡Cn/pm/p⋅Cn mod pm mod p(modp)C_n^m \equiv C_{n/p}^{m/p} \cdot C_{n \bmod p}^{m \bmod p} \pmod{p}CnmCn/pm/pCnmodpmmodp(modp)
其中 ppp 为质数。
n mod pn \bmod pnmodpm mod pm \bmod pmmodp 一定是小于 ppp 的数,可以直接求解,
Cn/pm/pC_{n/p}^{m/p}Cn/pm/p 可以继续用 Lucas 定理求解。
边界条件:当 m=0m = 0m=0 时,返回 111

typedef long long ll;
ll qpow(ll a,int b,ll p)
{
   ll res;
   while(b)
   {
    if(a & 1)res=res*a%p;
    a=a*a%p;
    b >>= 1;
   }
   return res;
}
void init(ll N,ll p)
{
    f[0]=1,g[0]=1;
    for(int i=1;i<N;i++)
    {
        f[i] = i*f[i-1]%p;
        g[i] = g[i-1]*qpow(i,p-2)%p;
    }
}
ll comb(ll n,ll m,ll p)
{
    return f[n]*g[n-m]%p*g[m]%p;
}
ll lucas(ll n,ll m)
{
    if(m==0)return 1;
    return lucas(n/p,m/p)*comb(n%p,m%p)%p;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值