6.0 KiB
一、定理内容
费马小定理 如果$p$是一个质数,而整数$a$不是$p$的倍数
\large a^{p}\equiv a (mod \ p)
\large \Leftrightarrow
\large a^{p-1} \equiv 1 (mod \ p)
1.1 这玩意有啥用?
题目:一个多位数有$2020$位,它左边三位为$123$。当这个数最大时,它被$29$除所得的余数是_______。
在小学奥数中,经常会用到费马小定理解决余数问题。
-
先举一个简单的例子:
比如计算
18^{111}除以$23$的余数是多少。在这里,$a=18$, $p=23$是素数,根据所以使用费马小定理,我们知道$18^{(23-1)}$除以$23$余$1$,即:
18^{111} \equiv 18^{22 \times 5 +1} \equiv 18^{22 \times 5 } \times 18 \equiv 18 (mod 23)所以答案就是:$18$。
-
回到这个问题: 这个$2020$位数最大时即为$123$后面是$2017$个$9$。 我们设这个数是$a$, 则$a= 124 \times 10^{2017}-1$。 这个问题就转变为$124 \times 10^{2017}-1$除以$29$的余数是多少? 我们用同余运算和费马小定理有:
124 \times 10^{2017} -1\equiv 8 \times 10^{72\times 28+1}-1这里因为$124$去除$29$,商是4,余数是$8$。商不会影响同余运算,舍去,保留余数8\equiv 8 \times 10^1 -1\equiv 80-1\equiv 21 (mod \ 29)答案就是:21怎么样,超简单吧?那么,如果$p$不是素数时,这种问题又该怎么处理呢? 这时候欧拉定理和欧拉函数就要大显身手啦。
1.2 可以用来降幂
a^n \ \% p = a^{n \ \% (p-1)} \ \% p
求$a$的$n$次方,可以先$n \ % (p-1)$
解释一下:
费马小定理降幂例子:
p=7
求2^{32}\ \%p
这个幂指数很大,我们不会傻傻的真的去计算出$2^{32}$,而是利用费马小定理对指数进行缩减:
因为$p=7$是质数,所以使用费马小降幂,p-1=6
2^6\%p=1
在$32$里面,把所有$6$的倍数去掉,就是2^{32-6*5}=2^2=4
也就是说,我们可以把原来的$32$降为4,最终的答案是一样的结果。
写成通用的格式就是:
求(a^N)mod(p)
a^{p-1} \equiv 1(mod \ p)
k=N \% (p-1)
(a^N)mod(p)=(a^k)mod(p)
二、例题I
求$2^{100}%13$的余数。
$2$的$100$次方,明显幂次挺大,而且$13$是质数,可以用费马小定理来降幂。
\large 2^{100} \equiv 2^{12*8+4} \equiv 16 \equiv 3 (mod \%13)
#include <bits/stdc++.h>
using namespace std;
//计算2^100 %13的值
int fun1(int a, int p, int mod) {
//前提p是质数,a不是p的倍数,有费马小定理
int ans = pow(a, p % (mod - 1)); //利用费马小定理,降幂 p'= p % (mod - 1),然后再计算 pow(a,p'),这个就小多了
ans %= mod; //再最后模一下mod就可以了
return ans;
}
// fun2和fun3都会受到时间或者空间的限制,而fun1直接使用了费马小定理可以迅速的求取答案
int fun2(int a, int p, int mod) {
int ans = pow(a, p); //不管p多大,我就是个算~暴力!但问题是你不怕爆掉int上界?不会溢出?
ans %= mod; //最后取模
return ans;
}
int fun3(int a, int p, int mod) {
int ans = 1;
while (p--) { //既然怕爆int上界,那么就一步一取模,一步一计算。笨的要死,你不怕慢死啊:人家费马小定理只需要一步,你需要p步!而且,p还不小,慢的要死!
ans *= a;
ans %= mod;
}
ans %= mod;
return ans;
}
int main() {
for (int i = 1; i <= 100; i++) {
cout << fun1(2, i, 13) << endl;
cout << fun2(2, i, 13) << endl;
cout << fun3(2, i, 13) << endl;
}
return 0;
}
二、例题II
解题思路
因为模数是$101$,比较小,而幂$n$是$2019^{2019}$,很大!所以使用费马小降幂$n%(p-1)$,这里$p$就是$101-1 = 100$;
int n = 1, ans = 0;
for (int i = 1; i <= 2019; i++) n = n * 2019 % 100;
也就是说,在不断的循环计算$n$的过程中,我们利用费马小定理,找到了一个n',使得$n'$与原数$n$对于结果的贡献是一样的,但$n'$明显小于n,方便计算出来结果。
实现代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 1, ans = 0;
for (int i = 1; i <= 2019; i++) n = n * 2019 % 100; //计算出n'
// 1~11的n次方
for (int i = 1; i <= 11; i++) {
int x = 1;
for (int j = 1; j <= n; j++) x = x * i % 101; //降幂后可以正常按要求计算
//收集答案
ans += x;
}
//最终也要模一下101
printf("%d\n", ans % 101);
return 0;
}
三、例题III
解题思路 项数比$mod$大很多,$2019$比$10086$小,所以不用费马小定理,用循环周期+快速幂做
实现代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 10086;
LL pow_mod(LL x, LL p) {
LL res = 1;
while (p) {
if (p & 1) res = res * x % mod;
p >>= 1;
x = x * x % mod;
}
return res;
}
LL ans;
LL n = 1e12;
int main() {
for (int i = 1; i <= mod; i++) ans = (ans + pow_mod(i, 2019)) % mod; //求到10086 一个循环周期的长度
ans = ans * (n / mod) % mod; //乘上倍数
n %= mod; //再加上余数
for (int i = 1; i <= n; i++) ans = (ans + pow_mod(i, 2019)) % mod;
printf("%lld\n", ans);
return 0;
}