要理解 O ( 1 ) O(1) O(1)快速乘,首先要知道一般的整数取模怎么用乘除法和减法实现,比如 a % b a\%b a%b,在整数除法下它等价于 a − a / b × b a-a/b\times b a−a/b×b。
那么这里快速乘的实现是什么样的呢?
代码:
inline ll mul(ll a,ll b,ll mod){
return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}
l o n g l o n g long long longlong类型的溢出其实相当于自动取相反数和自动取模。而快速乘的正确性保证就是取模的时候的模数是一定的(用脚趾头想都猜得到)
可以看到,这里用 l o n g d o u b l e long double longdouble类型计算出了 a × b / m o d a\times b/mod a×b/mod,同时避免了本来可能的一次溢出 a × b a\times b a×b,这次溢出是可能导致计算错误的(因为后面有除法,所以不能保证正确性),显然 a / m o d a/mod a/mod与 b b b相乘仍然可能导致溢出,但是这次溢出无关紧要。因为在取模可以进行除法。
而转回
l
o
n
g
l
o
n
g
long long
longlong就是为了最后能够取模。
(浮点数不可能取模)
而由于两边都可能有或没有溢出,而溢出时取的模数是一定的,所以我们将他们相见后可能会得到的答案一定在 64 64 64位带符号长整形范围内,并且只可能是正确的余数 r e m a i n d e r remainder remainder或 r e m a i n d e r − m o d remainder-mod remainder−mod。
实际上,笔者并不建议在比赛中使用
O
(
1
)
O(1)
O(1)快速乘,除非你特别需要卡常数,毕竟这浮点的精度问题总有点让人不放心。 (据说
D
Z
Y
O
DZYO
DZYO快速乘偶尔写的这个没有被卡过)