来自Andreas' answer:
这是一个古老的算法,它是精确的并且不会溢出,除非结果对于 long long 来说太大了
unsigned long long
choose(unsigned long long n, unsigned long long k) {
if (k > n) {
return 0;
}
unsigned long long r = 1;
for (unsigned long long d = 1; d <= k; ++d) {
r *= n--;
r /= d;
}
return r;
}
我认为这个算法也在 Knuth 的“计算机编程艺术,第 3 版,第 2 卷:半数值算法”中。
更新:算法溢出的可能性很小:
r *= n--;
对于非常大的n。一个天真的上限是 sqrt(std::numeric_limits<long long>::max()),这意味着 n 大约小于 4,000,000,000。
考虑 n == 67 和 k == 33。上述算法以 64 位 unsigned long long 溢出。然而,正确答案可以用 64 位表示:14,226,520,737,620,288,370。并且上面的算法对它的溢出保持沉默,choose(67, 33) 返回:
8,829,174,638,479,413
一个可信但不正确的答案。
不过,只要最终答案是可表示的,上述算法可以稍作修改,使其永远不会溢出。
诀窍在于认识到在每次迭代中,除法 r/d 是精确的。暂时重写:
r = r * n / d;
--n;
确切地说,这意味着如果您将 r、n 和 d 扩展为它们的素数分解,那么可以很容易地取消 d,并留下一个修改后的 n 值,称为 t,然后计算r 很简单:
// compute t from r, n and d
r = r * t;
--n;
一种快速简便的方法是找到 r 和 d 的最大公约数,称之为 g:
unsigned long long g = gcd(r, d);
// now one can divide both r and d by g without truncation
r /= g;
unsigned long long d_temp = d / g;
--n;
现在我们可以用 d_temp 和 n 做同样的事情(找到最大公约数)。但是,由于我们先验地知道 r * n / d 是精确的,所以我们也知道 gcd(d_temp, n) == d_temp,因此我们不需要计算它。所以我们可以将 n 除以 d_temp:
unsigned long long g = gcd(r, d);
// now one can divide both r and d by g without truncation
r /= g;
unsigned long long d_temp = d / g;
// now one can divide n by d/g without truncation
unsigned long long t = n / d_temp;
r = r * t;
--n;
清理:
unsigned long long
gcd(unsigned long long x, unsigned long long y)
{
while (y != 0)
{
unsigned long long t = x % y;
x = y;
y = t;
}
return x;
}
unsigned long long
choose(unsigned long long n, unsigned long long k)
{
if (k > n)
throw std::invalid_argument("invalid argument in choose");
unsigned long long r = 1;
for (unsigned long long d = 1; d <= k; ++d, --n)
{
unsigned long long g = gcd(r, d);
r /= g;
unsigned long long t = n / (d / g);
if (r > std::numeric_limits<unsigned long long>::max() / t)
throw std::overflow_error("overflow in choose");
r *= t;
}
return r;
}
现在您可以计算choose(67, 33) 而不会溢出。如果你尝试choose(68, 33),你会得到一个异常而不是错误的答案。