От Ответ Андреаса :
Вот древний алгоритм, который точен и не переполняется, если результат не слишком велик для 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;
}
Этот алгоритм также есть в книге Кнута «Искусство компьютерного программирования», 3-е издание, том 2: Получисленные алгоритмы ». Я думаю,
ОБНОВЛЕНИЕ: Существует небольшая вероятность того, что алгоритм переполнится в строке:
r *= n--;
для очень большой n. Наивная верхняя граница равна sqrt(std::numeric_limits<long long>::max())
, что означает n
меньше, чем грубые 4 000 000 000.
Рассмотрим n == 67 и k == 33. Приведенный выше алгоритм переполнен длинным длиной 64 бита без знака. И все же правильный ответ представлен в 64 битах: 14,226,520,737,620,288,370. И вышеупомянутый алгоритм ничего не говорит о его переполнении, выбор (67, 33) возвращает:
8,829,174,638,479,413
правдоподобный, но неверный ответ.
Однако приведенный выше алгоритм может быть слегка изменен, чтобы никогда не переполняться, пока окончательный ответ представим.
Хитрость заключается в том, что на каждой итерации деление r / d является точным. Временно переписываю:
r = r * n / d;
--n;
Чтобы быть точным, это означает, что если вы расширили r, n и d до их простых факторизаций, то можно легко отменить d и оставить его с измененным значением для n, назвать его t, а затем вычислить г просто:
// 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;
}
Теперь вы можете вычислять select (67, 33) без переполнения. И если вы попытаетесь выбрать (68, 33), вы получите исключение вместо неправильного ответа.