计算多项系数

时间:2014-04-06 09:24:45

标签: c++ combinatorics algebra

我想计算多项式系数mod 1e9 + 7.它等于:n! /(k0!* k1!* k2 * ... * km!)

在我的情况下m = 3,k0 + k1 + k2 = n,所以它将是:n! /(k0!* k1!* k2!)我的代码:

....
long long k2 = n - k1 - k0;
long long dans = fact[n] % MOD;
long long tmp = fact[i] % MOD;
tmp = (tmp * fact[j]) % MOD;
tmp = (tpm * fact[k]) % MOD;
res = (fact[n] / tmp) % MOD; // mb mistake is here...
cout << res;

事实[i] - i mod 1e9 + 7的阶乘 它不适用于大型测试

3 个答案:

答案 0 :(得分:3)

我希望我在这里没有链接,但这是一个工作过程,以解决你的问题:

  1. 天真的实现总是会遇到溢出错误。您必须准备好利用多项式系数的某些数学属性来达到稳健的解决方案。 Dave Barber library中使用递归属性(例如4个数字 - 当所有分支都被零替换时递归停止)

    multi (a, b, c, d) = multi (a − 1, b, c, d) + multi (a, b − 1, c, d) + multi (a, b, c − 1, d) + multi (a, b, c, d − 1)
    
  2. 基于以上所述, DavidSerranoMartínez显示了如何提供提供溢出控制的implementation。他的代码可以像

    一样轻松使用
    unsigned long long result_2 = multinomial::multi<unsigned long long>({9, 8, 4});
    
  3. 第三种选择是使用(或学习)专用于组合学的库,如SUBSET。由于依赖性和长度,这是一个更难阅读的代码,但是调用就像

    一样简单
    int factors[4] = {1, 2, 3, 4};
    Maths::Combinatorics::Arithmetic::multinomial(4, factors)
    

答案 1 :(得分:3)

您可以通过将分子与sum(ks)相乘并将分母从1分开来计算sum(ks)! / (k1! * ... * kn!)。进度结果将始终为整数,因为只有在第一次将i个连续整数相乘后,才除以​​i

def multinomial(*ks):
    """ Computes the multinomial coefficient of the given coefficients
    >>> multinomial(3, 3)
    20
    >>> multinomial(2, 2, 2)
    90
    """
    result = 1
    numerator = sum(ks)
    ks = list(ks)       # These two lines are unnecessary optimizations
    ks.remove(max(ks))  # and can be removed
    for k in ks:
        for i in range(k):
            result *= numerator
            result //= i + 1
            numerator -= 1
    return result

答案 2 :(得分:1)

我最近遇到过这个问题,我的解决方案是首先映射到日志空间,在那里完成工作,然后重新映射。这是有用的,因为我们避免了对数空间中的溢出问题,并且乘法也变得更有效的总和。直接使用对数空间结果也可能很有用。

数学:

C(x1, ..., xn) = sum(x)! / (x1! * ... * xn!)

因此

ln C(x1, ..., xn) = ln sum(x)! - ln {(x1! * ... * xn!)}
                  = sum{k=1->sum(x)} ln k - sum(ln xi!)
                  = sum{k=1->sum(x)} ln k - sum(sum{j=1->xi} (ln j))

如果xisum(x)中的任何一个很大(例如> 100),那么我们实际上可以使用Sterling的近似值:

ln x! ~= x * ln x - x

哪会给:

ln C(x1, ..., xn) ~= sum(x) * ln sum(x) - sum(x) - sum(xi * ln xi - xi)

这是代码。首先编写一个日志因子辅助函数是有帮助的。

#include <vector>
#include <algorithm>   // std::transform
#include <numeric>     // std::iota, std:: accumulate
#include <cmath>       // std::log
#include <type_traits> // std::enable_if_t, std::is_integral, std::is_floating_point

template <typename RealType, typename IntegerType,
          typename = std::enable_if_t<std::is_floating_point<RealType>::value>,
          typename = std::enable_if_t<std::is_integral<IntegerType>::value>>
RealType log_factorial(IntegerType x)
{
    if (x == 0 || x == 1) return 0;
    if (x == 2) return std::log(2); // can add more for efficiency

    if (x > 100) {
        return x * std::log(x) - x; // Stirling's approximation
    } else {
        std::vector<IntegerType> lx(x);
        std::iota(lx.begin(), lx.end(), 1);
        std::vector<RealType> tx(x);
        std::transform(lx.cbegin(), lx.cend(), tx.begin(),
                       [] (IntegerType a) { return std::log(static_cast<RealType>(a)); });
        return std::accumulate(tx.cbegin(), tx.cend(), RealType {});
    }
}

然后日志因子函数很简单:

template <typename RealType, typename IntegerType>
RealType log_multinomial_coefficient(std::initializer_list<IntegerType> il)
{
    std::vector<RealType> denoms(il.size());
    std::transform(il.begin(), il.end(), denoms.begin(), log_factorial<RealType, IntegerType>);
    return log_factorial<RealType>(std::accumulate(il.begin(), il.end(), IntegerType {})) -
            std::accumulate(denoms.cbegin(), denoms.cend(), RealType {});
}

最后是多项系数法:

template <typename RealType, typename IntegerType>
IntegerType multinomial_coefficient(std::initializer_list<IntegerType> il)
{
    return static_cast<IntegerType>(std::exp(log_multinomial_coefficient<RealType, IntegerType>(std::move(il))));
}

e.g。

cout << multinomial_coefficient<double, long long>({6, 3, 3, 5}) << endl; // 114354240

对于任何远大于此的输入,我们将使用内置类型溢出,但我们仍然可以获得对数空间结果,例如

cout << log_multinomial_coefficient<double>({6, 3, 11, 5, 10, 8}) << endl; // 65.1633