一、龟速乘
因其为其时间复杂度为O(logn) 所以又名龟速乘
- 快速幂和龟速乘法主要是用来解决中间运算结果过大导致long long类型都会溢出的问题,具有一定的相似之处。
- 快速幂将指数运算分解为乘法运算,而龟速乘法将乘法运算变为加法运算。
- 同理将k看做一个二进制数,a每次乘以2再根据二进制k的该位情况来决定是否将a加入结果,初始a对应二进制k的最低位(右)。
#include<iostream>
using namespace std;
typedef long long LL
const int mod = 1e9 + 7;
inlint LL slow_mul(LL a, LL k, LL p)
{
LL res = 0;
while(k)
{
if(k & 1)res = (res + a) % p;
k >>= 1;
a = 2 * a % p;
}
return res;
}
int main()
{
LL a, b;
cin >> a >> b;
cout << slow_mul(a, b, p);
return 0;
}
二、光速乘
因其时间复杂度为O(1),所以称为光速乘

模板
//a * b % mod
inline LL mul_mod(LL a,LL b, LL mod) //快速乘
{
return (a*b-(LL)((long double)a/mod*b)*mod+mod)%mod;
}
/*
* 光速乘步骤拆分
* () % p
* (a * b - c) % p
* c = (ll)(d + e)
* d = ((long double)a / mod * b) * p
* e = p;
*/
//在非负数的环境下可以用这个来卡常:
inline LL mul(LL a,LL b,LL p){
LL r = a*b-(LL)((long double)a/p*b+0.5)*p;
return r < 0 ? r + p : r;
}
long long 类型的溢出其实相当于自动取相反数和自动取模。而快速乘的正确性保证就是取模的时候的模数是一定的(用脚趾头想都猜得到)
可以看到,这里用 long double 类型计算出了 a * b % p,同时避免了本来可能的一次溢出a × b ,这次溢出是可能导致计算错误的(因为后面有除法,所以不能保证正确性),显然 a / mod 与 b 相乘仍然可能导致溢出,但是这次溢出无关紧要。因为在取模可以进行除法。
而转回 long long 就是为了最后能够取模。(浮点数不可能取模)
而由于两边都可能有或没有溢出,而溢出时取的模数是一定的,所以我们将他们相见后可能会得到的答案一定在 64 位带符号长整形范围内,并且只可能是正确的余数 remainder 或 remainder-mod
实际上,并不建议在比赛中使用 O(1) 快速乘,除非你特别需要卡常数,毕竟这浮点的精度问题总有点让人不放心。
三、快速乘总结
- 由来:
如果两个int相乘取模,相乘时可能会爆int,这时我们采用高一级的long long来计算。
如果两个long long相乘取模,要用更高一级容纳位数更多的手写高精度来计算(很麻烦)。
为了简便,人们发明了许多方法,我们称处理long long相乘取模的算法为“快速乘”。
快速乘一般有两种方法,一种是短小精悍的O(1)算法:光速乘,一种是精准无误的O(log N)算法(龟速乘)。 - 龟速乘思路:一个数字可以拆成许多个二进制位的0和1,即
,这样我们把b用加法和乘法的形式表示了出来。由于c的值只有0/1,所以我们就把乘法转变成了加法。
用分配律展开
,只要在每次计算后立刻取模,就可以在long long范围内解决。 - 光速乘思路:简单来说,
,注意正负就行。
- 区别:
(1)在效率上,显然光速乘的效率远高于龟速乘
(2)在精度上,因为光速乘使用了 long double 类型,所以光速乘的精度不一定可靠

549

被折叠的 条评论
为什么被折叠?



