如何计算 O(n) 中前 k 个二项式系数的总和而不会溢出?

Posted

技术标签:

【中文标题】如何计算 O(n) 中前 k 个二项式系数的总和而不会溢出?【英文标题】:How to compute sum of first k binomial coefficients in O(n) without overflow? 【发布时间】:2017-02-15 05:18:45 【问题描述】:

我正在尝试在O(N) 中计算此值而不会溢出(使用 C++)

为了澄清,nr 是事先给出的,我试图在O(N) 中找到(n,r) 对的一个实例的答案

这是我的试验:

    使用O(N)计算ans = n!/(r!(n-r)!2^n)c = ans,用O(N)修改cc /= (n-p); c*=(p+1)p = r-1 to 0。为每个步骤添加cans

基本上,我首先使用O(N) 计算最后一项,然后使用滑动窗口之类的东西来查找倒数第二个,然后是下一个……直到第一个。在这个过程中总结它们。

虽然看起来是正确的,但实际运行时间仍然比我预期的要慢。所以我想知道这个公式有什么特殊的已知技巧可以提高性能吗?如果没有,那么有没有办法减少常数因子? (基于以下sn-p)

另一个大问题是我面临一个困境:我不能单独计算 2^(-n)nCrn,否则它会下溢/溢出。这就是为什么我尝试计算2^(-n) * The last term in the summation希望效果会相互抵消,这样我就不会在整个过程中出现下溢/溢出。有什么方法可以100%保证不会下溢/上溢?

(如果可能,我想避免使用大整数库)

// c++ code snippet to demonstrate the idea 

double ans = 1;
for(int p=n; p>=1; p--)
  ans *= p;
  ans /= 2;
  if(p <= r) ans /= p;
  if(p <= n-r) ans /= p;

// now ans = n!/(r!(n-r)!2^n)
// use O(N) more time to find the ultimate ans: summation (n!/(r!(n-r)!2^n)) for r >= 0
double c = ans;
for(int p = r-1; p >= 0; p--)
  c /= (n-p);
  c *= (p+1);
  // Each new c is the next term:  n!/((r-1)!(n-r+1)!2^n)
  ans += c;

【问题讨论】:

等等... sum 并没有说明 summation 变量是什么。如果nr 都是常量,而求和中的表达式是n choose r,那么您似乎只是在对一个常量求和。 @user2357112 sum(nC0, nC1..nCr),这里的图不对,我去编辑一下 您使用double 是否意味着您对近似值感到满意? @rici yes 如果近似值的精度至少为 10^-8... mathoverflow.net/questions/17202/… 【参考方案1】:

计算并存储从 0 到 n 的每个 m 的 log(m!)。

计算对数(1/2**n)。

现在和的第 p 项是 exp(log(n!)-log(p!)-log((n-p)!)+log(1/2**n))。将这些术语加在一起。

【讨论】:

听起来不错的方法,试一试(虽然为了更容易实现,将使用base 2)【参考方案2】:

为什么不在第一个循环中开始求和呢?如果第一个循环可以正确计算二项式系数,您可以将它们相加并计算分母。目前您只计算ans=2^(-n),因为其他操作相互取消。

写出的总和是

1/2^n*(1+n+n*(n-1)/2+n*(n-1)*(n-2)/(2*3)+...)

两个二项式之间的商是

nC[k+1]/nC[k] = (n-k) / (k+1)

另请注意,根据二项式定理,上索引 rn-r-1 的部分和具有和 1

【讨论】:

如果我没有误会,您是说将ans 初始化为 2^(-n),然后使用一个循环进行求和,对吗?如果是这样的话,我这样做是因为我担心当 n 太大时, 2^(-n) 会下溢,所以我想将它与最大项 nCr 一起计算以“希望”它不会下溢也不会溢出 是的,那么您必须正确计算最大项。或者使用更大的n 部分二项式和的近似公式,请参阅密度和累积密度函数的伯努利分布和渐近近似。 +1 表示方程。问题是64位double上的迭代步骤很快就会溢出(n=54,r=27)以上n的所有总和都是不好的,因为迭代步骤不再适合双精度并且将被截断......这导致重大错误。例如 n=100,r=50double sum=684270969637826229800000000000/2^90bigint sum=680189586700239570843455122823/(2^90) 所以只有前 2 位数字是正确的。

以上是关于如何计算 O(n) 中前 k 个二项式系数的总和而不会溢出?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 memoization 计算二项式系数?

在 R 中计算 *integer* 二项式系数

如何找到固定 n 的前 r 二项式系数的总和?

《具体数学》——二项式系数

计算系数

计算系数