【问题标题】:Permutation with Repetition: Avoiding Overflow重复排列:避免溢出
【发布时间】:2011-12-21 14:45:38
【问题描述】:

背景:

给定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 以避免整数溢出尽可能长,并确保当我完成计算时,我要么有正确的值perm ,或者我知道最终结果会溢出?

基本上,我想避免使用 GNU GMP 之类的东西。

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

【问题讨论】:

  • 为什么要避免 GMP?一般来说,您希望做的工作尽可能少。
  • 检测溢出实际上是C语言的弱点之一。假设您设法尽可能长时间地避免溢出,因此可以确保您将获得正确的值,当且仅当可以计算它而不会溢出。即使这样,您仍然不会知道是否真的发生了溢出。
  • @Dave:你是对的。但是,这个问题很有趣。因此,对于那些关心“如何”而不是“为什么”的人来说,问题仍然存在:)。也许有人最终在交互式烤面包机的 8051 中使用它:P

标签: c overflow permutation integer-overflow


【解决方案1】:

这些被称为多项式系数,我将用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

那么使用递归来计算系数就很简单了。请注意,为避免指数级运行时间,您需要缓存结果(以避免重新计算)或使用动态规划。

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

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

【讨论】:

  • 这是有道理的。不过,肯定需要一些dynamic programming。 :-)
  • 是的,记忆是必须的:)
【解决方案2】:

如果你有很多 cpu 时间,你可以把所有的阶乘组成列表,然后找到列表中所有数字的素因数分解,然后将顶部的所有数字与底部的数字相消,直到数量完全减少。

【讨论】:

  • 你希望 N 有多大?
  • N 表示由编译器后端优化的“基本块”中的指令数。因此,如果有人编写一堆没有控制语句的代码 [但仍然是整数],N 可能会非常大。该算法可能对我的一位正在为不知名的 DSP 架构实施不知名的优化通道的同事很有用。避免 GMP 的需要更多的是一时兴起而不是要求。
  • 对于这个问题,这个算法足够快。然而,花在编写因式分解和取消算法上的时间是相当大的 IMO。
  • 同意。我现在将其标记为正确,因为它是一个干净且 IMO 优雅的解决方案。
【解决方案3】:

最安全的溢出方式是 Dave 建议的方式。您会找到素数 pn! 除以总和的指数

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

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

即使你使用 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 乘法,保持中间结果适度小。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      注解: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
      

      这样每次除法都会产生一个整数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-13
        • 2020-01-07
        • 2020-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多