AcWing - 90 - 64位整数乘法(数学基础)
题目链接
今天学习lyd的蓝书学到一种新的解法,以前只会用龟速乘做,没想到还可以利用整型溢出来做,真是太妙了!
1.首先是龟速乘的做法,和快速幂思想差不多,就不多说了
ll solve(ll a, ll b, ll p) {
ll res = a, ans = 0;
while(b) {
if (b&1) ans = (ans+res)%p;
res = (res<<1)%p;
b >>= 1;
}
return ans%p;
}
2.然后就是另一种解法了,首先,我们假设\(a\times b\pmod p\)不会溢出,那么这个公式等价于\(a\times b-\lfloor a\times b\div p\rfloor\times p\)(为什么要搞这么个公式过会儿再讲)。好了,现在我们考虑一下会溢出
的情况,因为补码的性质,两数相乘超过64个进制位的数自会自动溢出(相当于模unsigned long long),但是没有关西,因为我们要模p,而p最大不过1e18,所以我们的最终答案也一定不大于1e18,所以我们只要求出等式左半部分和右半部分的差就可以得到答案了。
现在我们最大的问题就是如何求\(\lfloor a\times b\div p\rfloor\)这一部分啦。这里有个巧妙的办法就是先让a和b去模p,那么a和b模p后的乘积一定不会大于\(p^2\),我们用long double来存这个数,long double的有效数字是18~19个数字,超过的话会自动舍弃低位数字,前面说过了,我们a和b模p之后的结果不大于\(p^2\),而之后我们还要除以p,所以只有前面p个数字对我们来说有价值的,刚好,long double的位数满足我们的需求!所以我们现在就能求出来等式两边的数了,这时候他们的差就是我们所要的结果。(可能有的同学还对利用溢出计算不太理解,你可以理解为我们对左右两边计算的结果模\(2^{64}\)然后做差)
ll solve(ll a, ll b, ll p) {
a %= p, b %= p;
ll c = (long double)a*b/p;
ll ans = a*b - c*p;
return (ans+p)%p;
}
3.我随后天真以为既然利用溢出计算,那么干嘛那么麻烦,这样不就行了吗hhhhhh
ll solve(ll a, ll b, ll p) {
a *= b;
if (a<0) a = -a;
return a%p;
}
嗯...然后喜提wonderful answer, 我们\(a\pmod p\)相当于求\(\lfloor a\div p\rfloor \times p\), 显然我们这么求溢出之后的a和原数根本不是一个数,那么相除的结果也不对(如果用long double)会面临精度不够的问
题,所以这就是为什么要用上面的公式了