我还没有评论的声誉,但我想指出,rock321987 的回答效果很好:
快速且正确直到并包括 C(62, 31)
但无法处理所有输出适合 uint64_t 的输入。作为证据,请尝试:
C(67, 33) = 14,226,520,737,620,288,370 (verify correctness and size)
不幸的是,另一个实现吐出不正确的 8,829,174,638,479,413。还有其他计算 nCr 的方法不会像这样中断,但这里真正的问题是没有尝试利用模数。
请注意 p = 10000007 是素数,这使我们能够利用所有整数都具有逆模 p 的事实,并且该逆是唯一的。此外,我们可以很快找到逆。另一个问题有一个关于如何做到这一点的答案here,我在下面复制了。
这很方便,因为:
- x/y mod p == x*(y inverse) mod p;和
- xy mod p == (x mod p)(y mod p)
稍微修改一下其他代码,将问题概括如下:
#include <iostream>
#include <assert.h>
// p MUST be prime and less than 2^63
uint64_t inverseModp(uint64_t a, uint64_t p) {
assert(p < (1ull << 63));
assert(a < p);
assert(a != 0);
uint64_t ex = p-2, result = 1;
while (ex > 0) {
if (ex % 2 == 1) {
result = (result*a) % p;
}
a = (a*a) % p;
ex /= 2;
}
return result;
}
// p MUST be prime
uint32_t nCrModp(uint32_t n, uint32_t r, uint32_t p)
{
assert(r <= n);
if (r > n-r) r = n-r;
if (r == 0) return 1;
if(n/p - (n-r)/p > r/p) return 0;
uint64_t result = 1; //intermediary results may overflow 32 bits
for (uint32_t i = n, x = 1; i > r; --i, ++x) {
if( i % p != 0) {
result *= i % p;
result %= p;
}
if( x % p != 0) {
result *= inverseModp(x % p, p);
result %= p;
}
}
return result;
}
int main() {
uint32_t smallPrime = 17;
uint32_t medNum = 3001;
uint32_t halfMedNum = medNum >> 1;
std::cout << nCrModp(medNum, halfMedNum, smallPrime) << std::endl;
uint32_t bigPrime = 4294967291ul; // 2^32-5 is largest prime < 2^32
uint32_t bigNum = 1ul << 24;
uint32_t halfBigNum = bigNum >> 1;
std::cout << nCrModp(bigNum, halfBigNum, bigPrime) << std::endl;
}
如果您愿意等待,这应该会为任何一组 32 位输入产生结果。为了证明这一点,我已经包含了 24 位 n 和最大 32 位素数的计算。我的普通电脑花了大约 13 秒来计算这个。对照wolfram alpha检查答案,但要注意它可能会超过那里的“标准计算时间”。
如果 p 远小于 (n-r) 其中 r