Как считать C(n, k) по модулю, избегая переполнения, если модуль не нужен?
В задаче ответ — это C(n,k), и он гарантированно влезает в 64 бита (по условию мал), но промежуточные n! огромны и переполняют long long ещё до деления. Как посчитать само значение C(n,k) точно, без модуля и без длинной арифметики, не словив переполнение?
2 ответа
Не вычисляй факториалы целиком — строй C(n,k) множительно, чередуя умножение и точное деление, чтобы значение никогда не превышало финальный (маленький) ответ:
C(n,k) = ∏_{i=1}^{k} (n−k+i)/i.
Ключ в том, что после i-го шага накопленное значение равно C(n−k+i, i) — это всегда целое, поэтому деление точное и без остатка.
unsigned long long binom(int n, int k) {
if (k < 0 || k > n) return 0;
k = min(k, n - k); // симметрия: меньше итераций и меньше рост
unsigned long long res = 1;
for (int i = 1; i <= k; i++) {
res = res / i * (n - k + i) // НЕ переполнит, т.к. res делится на i на этом шаге
+ (res % i) * (n - k + i) / i; // безопасный вариант, если порядок умножения важен
}
return res;
}
Простой и обычно достаточный вариант — res = res * (n-k+i) / i; (деление точное, т.к. промежуток — биномиальный коэффициент). Сложность O(k). Симметрия k = min(k, n−k) и удержание значения «маленьким» — главная защита от переполнения. Если даже ответ велик — нужен __int128 или длинная арифметика.
Тонкость: в res * (n-k+i) / i промежуточное произведение res * (n-k+i) может переполнить, даже если итог мал — потому что res на этом шаге уже близок к ответу, а домножение временно его раздувает до деления. Если ответ близок к границе 64 бит, считай через __int128 для промежутка: res = (unsigned long long)((__int128)res * (n-k+i) / i); — это убирает риск переполнения промежуточного произведения.