组合数计算

1.组合数计算C _{n}^{m};

(1)通过定义式直接计算

根据定义C_{n}^{m}=\frac{n!}{m!(n-m)!},将n!、m!和(n-m)!计算出来即可;

long long C(long long n,long long m)
{
    long long ans=1;
    for(int i=1;i<=n;i++)   
        ans*=i;
    for(int i=1;i<=m;i++)
       ans/=i;
    for(int i=1;i<=n-m;i++)
        ans/=i;
    return ans;
}

很明显,即使用long long型来保存,也只能承受n<=20的数据;

(2)通过递推公式计算(O(n^2))

由于C_{n}^{m}表示从n个不同的数中选m个数的方案数,可以转换为两种选法方案数之和:一是不选最后一个数,从前n-1个数选m个数;二是选最后一个数,从前n-1个数选m-1,于是得到递推公式:

                                                                   C_{n}^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}

临界条件是,当m为0时或者m==n时,此时返回1

ll C(ll n,ll m)
{
    if(m==0||n==m)    return 1;
    return C(n-1,m)+C(n-1,m-1);
}

 这样不涉及阶乘计算,但是在递归过程中,我们可以发现会出现重复计算的情况。所以我们尝试用数组保存每次计算的组合数,如果有相同直接赋值即可;

ll res[70][70]={0,}
ll C(ll n,ll m)
{
    if(m==0||n==m)    return 1;
    if(res[n][m]!=0)      return res[n][m]
    return res[n][m]=C(n-1,m)+C(n-1,m-1);
}

也可不使用递归,使用递推公式.

(3)通过定义的变形来计算(O(m))

由组合数的定义,可以进行化简C_{n}^{m}=\frac{n!}{m!(n-m)!}=\frac{n*(n-1)*...*(n-m+1)}{1*2*...*m},经过观察可以发现,分子分母都是m项,可以进行变形C_{n}^{m}=\frac{n*(n-1)*...*(n-m+1)}{1*2*...*m}=\frac{\frac{\frac{\frac{(n-m+1)}{1}*(n-m+2)}{2}*...}{...}*(n-m+m)}{m},即边乘边除的方法

由此写出代码:

ll C(ll n,ll m)
{
    ll ans=1;
    for(ll i=1;i<=m;i++)
        ans=ans*(n-m+i)/i;
    return ans;
}

 虽然复杂度是O(m),但是程序有可能在最后一个乘法溢出,所以和(2)的范围基本相同。

列如方法2在n=67,m=33时开始溢出,而方法3在n=62,m=31时开始溢出。

2.计算C_{n}^{m}%p%p

(1)通过递推公式计算

基于第一个问题的方法(2),是最容易、最实用的一种。只需在原先的地方对p取摸即可。

int res[1010][1010]={0,}
int C(int n,int m,int p)
{
    if(m==0||n==m)    return 1;
    if(res[n][m]!=0)      return res[n][m]
    return res[n][m]=(C(n-1,m)+C(n-1,m-1))%p;
}

该算法支持m<=n<=1000,并且对于p的大小和素性没有额外限制(p<=10^9)。

(2)根据定义式计算(O(klog n)

将组合数进行质因子分解,然后利用快速幂计算每组,最后相乘再取一次模即可

质因子个数参考:

 https://blog.csdn.net/qq_40725780/article/details/104210156

快速幂参加:

https://blog.csdn.net/qq_40725780/article/details/104237011

int prime[maxn]    //素数表,注意表的最大素数不得小于n;

int C(int n,int m,int p)
{
    int ans=1;
    for(int i=0;prime[i]<=n;i++){
        int c=cal(n,prime[i])-cal(m,prime[i])-cal(n-m,prime[i]);    //获得prime[i]的指数
        ans=ans*binaryPow(prime[i],c,p)%p;
    }
    return ans;
}

 该算法支持m<=n<=10^6,并且对p的大小和素性没有额外限制

(3)通过定义式的变形计算

分情况讨论:

1.m<p,且是素数;(O(mlog m)

该做法基于第一个问题方法(3),但是不能在除法时直接模上p,因为每次的ans实际上已经取过模了。因此如果p是素数,可以使用扩展欧几里得算法或者费马小定理求出i模p的逆元;需要注意的时,此时必须满足m<p,否则中间求逆元过程可能失败

(p为素数,m<p, 两个条件都是确保逆元求解,保证gcd(m,p)=1)                                                                                                                                                                

 逆元求解参考:

https://blog.csdn.net/qq_40725780/article/details/104225432

int C(int n,int m,int p)
{
    int ans=1;
    for(int i=1;i<=m;i++)
    {
        ans=ans*(n-m+1)%p;
        ans=ans*inverse(i,p)%p;
    }
    return ans;
}

该算法支持m<=10^5的情况,且p必须为素数。

2.m任意(m>=p),p是素数(O(mlog n)

(这里情况下,如果按1的思想,不能保证在m的变换过程中,inverse(m,p)均可求)

C_{n}^{m}=\frac{n!}{m!(n-m)!}=\frac{n*(n-1)*...*(n-m+1)}{1*2*...*m}

因为m>=p,那么我们对组合数的的分子、分母求质因子的个数a,b。如果a>b,那么组合数的结果一定是p的倍数,直接返回0;如果a<=b,那么将分子中的p全部除去,保证分母的每个数都与p互质,即可正常计算

int C(int n,int m,int p)
{
    int ans=1,nump=0;
    for(int i=1;i<=m;i++){
        int temp=n-m+1    //分子;
        while(temp%p==0){
            nump++;
            tmep/=p;
        }
        ans=ans*temp%p;    //获得分子中不含p的部分相乘;
        temp=i;    //分母;
        while(temp%p==0){
            nump--;
            tmep/=p;
        }
        ans=ans*inverse(temp,p)%p;    //除以分母中除了p以外的部分;
    }
    if(nump>0)    return 0;
    else    retunr ans;
}
   

该算法支持m<=10^5,且p必须是素数

3.m任意,p任意

对分子,分母进行质因子分解获得化简后的结果,然后利用快速幂直接对p取模

(4)Lucas定理 

p是素数(p<=10^5)

int Lucas(int n,int m)
{
    if(m==0) return 1;
    return C(n%p,m%p)*Lucas(n/p,m/p)%p;
}

这里的C可以方法二或者方法三情况1

 

 

总结情况

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值