求
a
a
a乘
b
b
b对
p
p
p取模的值
输入格式
第一行输入整数
a
a
a,第二行输入整数
b
b
b,第三行输入整数
p
p
p
输出格式
输出一个整数,表示
a
×
b
m
o
d
p
a \times b\ mod\ p
a×b mod p
数据范围
1
≤
a
,
b
,
p
≤
1
0
18
1 \leq a,b,p \leq 10^{18}
1≤a,b,p≤1018
输入样例
3
4
5
输出样例
2
①分析
这题使用到的算法为龟速乘,类似于快速幂,也可以用高精度
a × b m o d p 这里相当于 b 个 a 相加 m o d p ,即用加法来表示乘方 因为数据范围很大,相乘就会爆 l o n g l o n g ,因而要避免让两个数发生乘法 但如果直接一个一个加,加一次就模上一个 p ,这样时间复杂度为 O ( b ) = 1 0 18 超时,需要降低 1 2 ,故考虑二进制 a m o d p 2 a m o d p 4 a m o d p 8 a m o d p … … 2 62 a m o d p 发现下一个可以由上一个的二倍得到,类似于二进制 可以将 b 转化为二进制表示,设为 ( 10101 ) 2 , b = 2 0 + 2 2 + 2 4 a × b = a + 4 a + 16 a 因而只需要 a 每次乘以二倍模上 p 迭代,然后看 b 的二进制哪些位上为 1 ,为 1 结果就加上当前 a 的值,为 0 则跳过 复杂度就降为了 O ( l o g b ) a\times b\ mod\ p这里相当于\ b\ 个\ a\ 相加\ mod\ p,即用加法来表示乘方\\ 因为数据范围很大,相乘就会爆long\ long,因而要避免让两个数发生乘法\\ 但如果直接一个一个加,加一次就模上一个p,这样时间复杂度为O(b)=10^{18}超时,需要降低\frac{1}{2},故考虑二进制\\ a\ mod\ p\\ 2a\ mod\ p\\ 4a\ mod\ p\\ 8a\ mod\ p\\ ……\\ 2^{62}a\ mod\ p\\ 发现下一个可以由上一个的二倍得到,类似于二进制\\ 可以将b转化为二进制表示,设为(10101)_2,b=2^0+2^2+2^4\\ a\times b=a+4a+16a\\ 因而只需要a每次乘以二倍模上p迭代,然后看b的二进制哪些位上为1,为1结果就加上当前a的值,为0则跳过\\ 复杂度就降为了O(logb) a×b mod p这里相当于 b 个 a 相加 mod p,即用加法来表示乘方因为数据范围很大,相乘就会爆long long,因而要避免让两个数发生乘法但如果直接一个一个加,加一次就模上一个p,这样时间复杂度为O(b)=1018超时,需要降低21,故考虑二进制a mod p2a mod p4a mod p8a mod p……262a mod p发现下一个可以由上一个的二倍得到,类似于二进制可以将b转化为二进制表示,设为(10101)2,b=20+22+24a×b=a+4a+16a因而只需要a每次乘以二倍模上p迭代,然后看b的二进制哪些位上为1,为1结果就加上当前a的值,为0则跳过复杂度就降为了O(logb)
②代码
#include <cstdio>
typedef long long LL;
LL mul(LL a, LL b, LL p)
{
LL res = 0;
while (b)
{
if (b & 1)
res = (res + a) % p;
a = 2 * a % p;
b >>= 1;
}
return res;
}
int main()
{
LL a, b, p;
scanf("%lld%lld%lld", &a, &b, &p);
printf("%lld\n", mul(a, b, p));
return 0;
}
③细节分析
LL mul(LL a, LL b, LL p)
{
LL res = 0;
while (b)
{
if (b & 1) // 判断当前位是否为1
res = (res + a) % p;
a = 2 * a % p; // b右移的同时a每次乘2即可得到a、2a...
b >>= 1;
}
return res;
}