组合数算法-解决溢出问题

组合数的定义:C(n,m)=n!/( (n-m)!*m! )

计算组合数主要头疼的是溢出,long long 类型的数字算C(82,41)已经不行了。。。

一、普通算法

    由于溢出问题严重,所以算出三个阶乘再做除法的话,中间结果会溢出。

    首先做个小优化,利用 C(n,m) = C(n,n-m) ,如果m超过n的一半就让 m = n-m。

    这样处理之后,m一定是小于等于n-m的

    对于:1,2,3,…,m,m+1,…,n-m,…,n

    公式中 n! , (n-m)!, m!肯定有重叠的部分,所以把n!和(n-m)!中重叠部分消去,直接算(n-m+1)*(n-m+2)*…*n / m!(红色部分的连乘)就好,这样时间复杂度是O( m ),已经和n没有关系了,而且就算要计算 C(10000,3)也可以,因为中间结果最大是1000*999*998。

    还有个小优化,为了防止溢出,一边计算 (n-m+1)*(n-m+2)*…*n ,一边除掉1,2,3,...,m中能除的数(下面的算法是按顺序除的)

 

long long cnm(int n,int m)
{
    long long s = 1;
    int k = 1;
    if(m > n/2)
        m = n-m;
    for(int i=n-m+1;i<=n;i++)
    {
        s *= (long long)i;
        while(k<=m && s%k == 0)
        {
            s /= (long long)k;
            k++;
        }
    }
    return s;
}


二、对数算法

    这名字是我自己起的,就是利用对数解决溢出问题。

    为了避免直接计算n的阶乘,对公式两边取对数,于是得到:ln(C(n,m)) = ln(n!) - ln(m!) - ln( (n-m)! )

    对数有性质:ln(x*y) = ln(x) + ln(y),因此转化成:

   

    同理消去重叠的部分,就变成了

   

    因此这个算法时间复杂度仍然是 O( m ),虽然浮点计算比整数计算要慢,但解决了整数计算的溢出问题。

double cnm_lg(int n,int m)
{
    int i;
    double s1=0.0,s2=0.0;
    for(i=1;i<=m;i++)
        s1 += log(i);
    for(i=n-m+1;i<=n;i++)
        s2 += log(i);
    return s2-s1;
}

double cnm_double(int n,int m)
{
    if(m > n/2)
        m = n-m;
    return exp(cnm_lg(n,m));
}

 

    有了浮点数的帮忙,我们已经可以计算结果在double表示范围(10^308我记得是)以内的组合数了,不过double类型会损失精度,我还有个想法是单用一个int数记录结尾0的个数(由于结果末尾会有很多0),double表示前面的数,不过这个算法的结果总是先保存在double类型里的,还要想别的办法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值