模运算
定义:设
a
,
b
∈
Z
a,b\in Z
a,b∈Z且
b
>
0
b>0
b>0,如果
q
,
r
∈
Z
q,r\in Z
q,r∈Z满足
a
=
q
b
+
r
a=qb+r
a=qb+r且
0
≤
r
<
b
0\le r<b
0≤r<b则称
a
a
a模
b
b
b等于
r
r
r记作
a
m
o
d
b
=
r
a\space mod \space b=r
a mod b=r
负数取模
将 a a a不断的加上 b b b知道结果大于 0 0 0,这个结果就是模运算的结果
模运算的性质
- b ∣ a ⇔ a m o d b = 0 b\mid a\Leftrightarrow a\space mod\space b=0 b∣a⇔a mod b=0
- ( a + b ) m o d n = ( a m o d n + b m o d n ) m o d n (a+b)mod\space n=(a\space mod \space n+b\space mod\space n)mod\space n (a+b)mod n=(a mod n+b mod n)mod n
- ( a − b ) m o d n = ( a m o d n − b m o d n ) m o d n (a-b)mod\space n=(a\space mod \space n-b\space mod\space n)mod\space n (a−b)mod n=(a mod n−b mod n)mod n
- ( a × b ) m o d n = ( a m o d n × b m o d n ) m o d n (a\times b)mod\space n=(a\space mod \space n\times b\space mod\space n)mod\space n (a×b)mod n=(a mod n×b mod n)mod n
- 除法不具有这样的性质
在快速幂取模中每一步计算都可以进行一次取模,以简化中间结果,防止溢出
快速幂
为什么要用快速幂
计算 a b m o d p a^b\space mod\space p ab mod p,其中 a , b a,b a,b是非常大的数。这时如果将 a b a^b ab一个一个累乘计算的话会很慢,尽管他的时间复杂度是 O ( n ) O(n) O(n),并且这样一个大的数字很难存储在计算机中( i n t , l o n g l o n g int,long long int,longlong都会溢出),这时就需要快速幂来解决这个问题。
在 R S A RSA RSA加密中也使用了快速幂算法
原理
快速幂其实就是利用了分治的思想,将 a b a^b ab分成 a b / 2 a^{b/2} ab/2和 a b / 2 a^{b/2} ab/2两个部分,一直细分,直到幂次 b = 1 b=1 b=1,再将这些结果累乘起来,并且利用模运算的性质,在累乘过程中不断取模,防止溢出,最后就可以得到最后的答案。
递归实现
递归的思路非常简单,常规。
typedef unsigned long long ull;
ull a, n, p;
ull q_pow(ull a, ull n,ull p) {
if (n == 1) return a;
ull c = q_pow(a, n >> 1, p) % p;//右移一位代表除二,位运算符在计算机中效率较高
if (n & 1) return c * c % p * a % p;
return c * c % p;
}
非递归实现
非递归需要利用到二进制的知识。对于任意一个十进制数字我们都可以把它写成二进制累加的形式,下面我们举个例子说明:
5
=
2
2
+
2
0
5=2^2+2^0
5=22+20
所以,我们就可以把幂
b
b
b写成一个二进制累加的形式,再利用幂的运算规则,就可以将
a
a
a写成一串数的乘积,
3
5
=
3
2
2
+
2
0
=
3
2
2
×
3
2
0
3^5=3^{2^2+2^0}=3^{2^2}\times 3^{2^0}
35=322+20=322×320
这些乘数的幂次都对应着
b
b
b的一个二进制位,我将幂次写成二进制的形式
3
(
101
)
2
=
3
(
100
)
2
+
(
001
)
2
3^{(101)_2}=3^{(100)_2+(001)_2}
3(101)2=3(100)2+(001)2,每个二进制位为
1
1
1的位置,就是上式中乘数的幂次。
写代码时,我们就可以遍历幂次
b
b
b的二进制位,等于
1
1
1的就将其乘起来,并且不断取模保证不溢出,最后遍历完
b
b
b的二进制位,循环结束,得到的乘积就是结果。
代码实现
typedef unsigned long long ull;
ull a, b, p;
ull q_pow(ull a, ull b, ull p) {
ull res = 1;
while (b > 0) {
if (b & 1) res = res * a % p;
a = a * a % p;//b每右移一位,a对应的也要平方
b >>= 1;
}
return res;
}