重复排列:避免溢出

时间:2011-12-21 14:45:39

标签: c overflow permutation integer-overflow

背景:

给予n球以下:

'a' balls are of colour GREEN
'b' balls are of colour BLUE
'c' balls are of colour RED
...

(当然a + b + c + ... = n

可以排列这些球的排列数由下式给出:

perm = n! / (a! b! c! ..)

问题1: 我如何“优雅地”计算perm以避免整数溢出尽可能长,并确保当我完成计算时,我要么具有正确的值{ {1}},或者知道最终结果会溢出吗?

基本上,我想避免使用像GNU GMP这样的东西。

可选,问题2: 这是真的坏主意,我应该继续使用GMP吗?

5 个答案:

答案 0 :(得分:6)

这些被称为多项系数,我将用m(a,b,...)表示。

你可以通过利用这个身份(这应该很容易证明)来有效地计算它们以避免溢出:

m(a,b,c,...) = m(a-1,b,c,...) + m(a,b-1,c,...) + m(a,b,c-1,...) + ...
m(0,0,0,...) = 1 // base case
m(anything negative) = 0 // base case 2

然后使用递归来计算系数是一件简单的事情。请注意,为了避免指数运行时间,您需要缓存结果(以避免重新计算)或使用动态编程。

要检查溢出,只需确保总和不会溢出。

是的,使用任意精度库来完成这个简单的任务是一个非常糟糕的主意。

答案 1 :(得分:5)

如果你有大量的cpu时间,你可以从所有因子中做出列表,然后找到列表中所有数字的素数因子分解,然后取消顶部的所有数字与底部的数字,直到数字完全减少了。

答案 2 :(得分:3)

溢出最安全的方式是戴夫建议的方式。您可以通过求和找到素数p除以n!的指数

m = n;
e = 0;
do{
    m /= p;
    e += m;
}while(m > 0);

p的因子分数中减去a!的指数等。对所有素数<= n执行此操作,并且您具有多项式系数的因子分解。当且仅当最终结果溢出时,该计算才会溢出。但是多项系数增长得相当快,所以对于相当小的n,你已经有了溢出。对于大量计算,您将需要一个bignum库(如果您不需要精确的结果,使用double可以使用更长的时间)。

即使你使用了一个bignum库,也值得保持中间结果不会太大,所以不要计算阶乘和分割大数,最好按顺序计算部分,

n!/(a! * b! * c! * ...) = n! / (a! * (n-a)!) * (n-a)! / (b! * (n-a-b)!) * ...

并计算这些因素中的每一个,让我们以第二个为例进行说明,

(n-a)! / (b! * (n-a-b)!) = \prod_{i = 1}^b (n-a+1-i)/i

计算
prod = 1
for i = 1 to b
    prod = prod * (n-a+1-i)
    prod = prod / i

最后将这些部分相乘。这需要n次除法和n + number_of_parts - 1次乘法,保持中间结果适度小。

答案 3 :(得分:1)

根据this link,你可以计算多项系数作为几个二项式系数的乘积,控制整数溢出。

这将原始问题减少为二项式系数的溢出控制计算。

答案 4 :(得分:-2)

符号:n! = prod(1,n)你可能猜到了什么。

这很容易,但首先你必须知道,对于任何2个正整数(i, n > 0),表达式是一个正整数:

prod(i,i+n-1)/prod(1,n)

因此,我们的想法是将n!的计算切片为小块并尽快划分。

使用a,而不是使用b等等。

perm = (a!/a!) * (prod(a+1, a+b)/b!) * ... * (prod(a+b+c+...y+1,n)/z!)

这些因素中的每一个都是整数,因此如果perm不会溢出,则其中任何一个因素都不会溢出。

虽然,在计算这样一个因子时,可能是分子或分母的溢出但是可以避免在分子中进行乘法,然后是交替的除法:

prod(a+1, a+b)/b! = (a+1)(a+2)/2*(a+3)/3*..*(a+b)/b

这样每个分区都会产生一个整数。