如何在C或C++中计算(N选择K)%M,避免溢出?
特别地,当N (4<=N<=1000),K (1<=K<=N),且M = 1000003时。
要计算(n choose k) % M,可以分别计算被除数(n!)模M和除数(k!*(n - k)!)模M,然后将被除数乘以除数的模逆元素(在M下)。由于M是质数,因此可以使用费马小定理来计算乘法逆元。
以下链接(问题SuperSum)提供了一个很好的解释和示例代码:
由于1000000003 = 23 * 307 * 141623,您可以计算 (n chooses k) mod 23、307 和 141623,然后应用中国剩余定理[1]。在计算 n!、k! 和 (n-k)! 时,每一步都应该计算模数为23、307 和 141623 的所有内容,以防止溢出。
这样即使在32位机器上也可以避免溢出。
稍微改进一下是计算 (n chooses k) mod 141623 和 7061 (23 * 307) (编辑:但计算模反数7061可能有点棘手,所以我不会这样做)
对我的糟糕英语感到抱歉。
[1]http://en.wikipedia.org/wiki/Chinese_remainder_theorem
编辑2:我发现另一个潜在的问题是,在计算 n! mod 23(例如)时,它很可能是0,但这并不意味着 (n chooses k) mod 23 是0,因此您应该在计算 (n chooses k) 之前计算23除 n!、(n-k)! 和 k! 的次数。计算这个很容易,p 恰好被 n! 整除 floor(n/p) + floor(n/p²) + ... 次。如果恰好有23个数被 n!、k! 和 (n-k)! 整除,那么您就可以通过每次除以23的倍数来计算 (n chooses k) mod 23。307也是如此,但141623则不然。
这里是一个简单的例子:
(A * B * C) % N ... is equal to... ((A % N) * (B % N) * (C % N)) % N;
也就是说,您需要对每个操作数和乘积应用模运算,或者在它变成一个大数时进行模运算。最后,模数必须应用于整体结果。
((1000000000%N)*(1000)%N)%N
。 - Nawaz1000000000%N
仍然是1000000000
。为避免溢出,您需要一个像N ^ 2一样大的整数类型(例如,如果可用,则为long long
或int64_t
)。 - Steve Jessop