移位运算例题
求 a 乘 b 对 p 取模的值,其中 1 ≤ a , b , p ≤ 1 0 18 求a乘b对p取模的值,其中1 \le a,b,p \le 10^{18} 求a乘b对p取模的值,其中1≤a,b,p≤1018
方法一
类似于快速幂的思想,把整数b用二进制表示,即
b
=
c
k
−
1
∗
2
k
−
1
+
c
k
−
2
∗
2
k
−
2
+
⋯
+
c
0
∗
2
0
b=c_{k-1}*2^{k-1}+c_{k-2}*2^{k-2}+ \cdots + c_0*2^0
b=ck−1∗2k−1+ck−2∗2k−2+⋯+c0∗20
那么
a
∗
b
=
c
k
−
1
∗
a
∗
2
k
−
1
+
c
k
−
2
∗
a
∗
2
k
−
2
+
⋯
+
c
0
∗
a
∗
2
0
a*b=c_{k-1}*a*2^{k-1}+c_{k-2}*a*2^{k-2}+ \cdots + c_0*a*2^0
a∗b=ck−1∗a∗2k−1+ck−2∗a∗2k−2+⋯+c0∗a∗20
因为
a
∗
2
i
=
(
a
∗
2
i
−
1
)
∗
2
a*2^i=(a*2^{i-1})*2
a∗2i=(a∗2i−1)∗2
若已求出 a ∗ 2 i − 1 m o d p a*2^{i-1}\mod p a∗2i−1modp ,则计算 $ (a*2^{i-1})*2 \mod p$ 时 ,运算过程中的每一步都不超过 2 ∗ 1 0 1 8 2*10^18 2∗1018,在64位整数 longlong的表示范围内,很容易通过k次递推求出每个乘机项。当 c i = 1 c_i=1 ci=1时,把该成绩项累加到答案中即可,时间复杂度为 O ( l o g 2 b ) O(log_2b) O(log2b)
代码
long long mul(long long a, long long b, long long p)
{
long long ans = 0;
for(;b;b>>=1)
{
if(b&1)
ans = (ans+1)%p;
a = a*2%p;
}
return ans;
}
方法二
利用 a ∗ b m o d p = a ∗ b − [ a ∗ b / p ] ∗ p a*b\mod p = a*b-[a*b/p]*p a∗bmodp=a∗b−[a∗b/p]∗p,其中[ ]表示下取整
首先,当 a , b < p a,b<p a,b<p时, a ∗ b / p a*b/p a∗b/p下取整后也一定小于p。我们可以利用浮点数执行 a ∗ b / p a*b/p a∗b/p的运算,而不用关心小数点之后的部分。浮点类型 long double 在十进制下有效数字有18 到19位,足够胜任。当浮点数精度不足以保存精确数值时,它会像科学计数法一样舍弃低位,正好符合需求
另外虽然 a ∗ b a*b a∗b和 [ a ∗ b / p ] ∗ p [a*b/p]*p [a∗b/p]∗p的可能很大,但是二者的差一定在0到 p − 1 p-1 p−1之间,我们只关心他们较低的几位即可。因此,我们可以用 long long 来保存 a ∗ b a*b a∗b和 [ a ∗ b / p ] ∗ p [a*b/p]*p [a∗b/p]∗p各自的结果。整数运算一处相当于舍弃高位,也符合需求
代码
long long mul(long long a, long long b, long long p)
{
a %= p , b%=p;//当a,b一定在0~p之间时,此行不必要
long long c = (long double)a*b/p;
long long ans = a*b-c*p;
if(ans<0)
ans += p;
else if(ans>=p)
ans -= p;
return ans;
}