アンドレアスの答えから:
これは、結果が大きすぎない限り、正確でオーバーフローしない古代のアルゴリズムです。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 の「The Art of Computer Programming, 3rd Edition, Volume 2: Seminumerical Algorithms」にもあると思います。
更新:アルゴリズムが回線上でオーバーフローする可能性がわずかにあります。
r *= n--;
非常に大きなn の場合。ナイーブな上限は、およそ 4,000,000,000 未満でsqrt(std::numeric_limits<long long>::max())
あることを意味します。n
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) を試すと、間違った答えではなく例外が発生します。