【算法笔记】Euler-Fermat 定理
- 本文总计约 13000 字,阅读大约需要 40 分钟。
- 警告!警告!警告!本文有大量的 公式渲染,可能会导致加载异常缓慢!
前言
如果一个人不学习 Euler 定理的话,我是绝对不会说他学过数论的。
作为数论中最基础的知识点,Euler 定理与 Fermat 小定理,Wilson 定理和中国剩余定理(CRT)并成为数论四大基本定理,它的重要性可见一斑。
后来又有数学家,将 Euler 定理做了一个拓展,得到了现在被我们广泛使用的定理— —扩展 Euler 定理,这也就是我们今天所要讲的知识。
扩展 Euler 定理可以把传统的快速幂加速得更快,同时,在研究群论的时候,Euler 定理也扮演着非常重要的角色。
所以,学习扩展 Euler 定理并不只是为了做 OI 题目的,它更是一块让我们研究更高层次的知识的垫脚石。
问题引入
给定正整数 ,求 的值。
:,;
:,;
:,。
从 Fermat 小定理开始
对于上面的 ,我们可以直接写一个快速幂。时间复杂度为 。
代码实现起来很简单:
#include <cstdio>
using namespace std;
typedef long long ll;
ll a, b, m;
ll qpow(ll a, ll b, ll m) { //快速幂模板
ll ans = 1 % m;
while(b) {
if(b & 1) ans = (ans * a) % m;
a = (a * a) % m; b >>= 1;
}
return ans;
}
int main(void) {
scanf("%lld%lld%lld", &a, &b, &m);
printf("%lld", qpow(a, b, m));
return 0;
}
//by CaO
既然快速幂时间复杂度当然是 ,当 时,还是可以轻松水过的。
我们来看 。
在 中, 的值达到了 ,这就意味着,即使是用 的快速幂,在这个时候也要执行 步以上而且还要打烦人的高精度 QwQ,所以是妥妥的 TLE。
但是我们又注意到,,它是一个质数。提起质数,我们应该想到什么?
当然是 Fermat 小定理:
Fermat 小定理:若 是质数且 ,则 。
我们再对原算式 做个变型:
对 对 做带余除法,得到 ,那么我们就有:
这也就是说,无论 有多大,只要 与 互质,我们都可以将 边读入,边取模,最后进行计算的时候, 的值一定不会超过 ,也就是说,在这种情况下,我们把原本的 的时间复杂度优化成了 ,当 的时候,无论 有多大,都可以轻松水过 QwQ。
代码只要基于原来的快速幂做一些小小的改动就可以:
#include <cstdio>
using namespace std;
typedef long long ll;
const int MOD = 998244353; //Subtask 2 保证模数为 998244353
ll a, b, m;
char ch;
ll read(void) { //快读,可以边读入边取模
ll w = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') {
w = (w * 10 + ch - '0') % (MOD - 1); ch = getchar();
}
return w;
}
ll qpow(ll a, ll b, ll m) {
ll ans = 1;
while(b) {
if(b & 1) ans = (ans * a) % m;
b >>= 1; a = (a * a) % m;
}
return ans;
}
int main(void) {
a = read(), b = read(), m = read();
printf("%lld", qpow(a, b, MOD));
return 0;
}
//by CaO
上面的代码是基于 是质数且 互质的情况下给出的,但如果 不是质数呢?
例如 ,则 ,但 ,两者显然不相等。这个时候,直接用 Fermat 小定理就不可行了 QwQ。
但是 又没有保证 一定是质数,更没有保证 ,怎么办呢?
这就是接下来,我们所要谈论的问题。
Euler 函数
我们定义 Euler 函数为 ,其中 , 代表从 中,与 互质的整数的个数。
例如 ,因为 中,与 互质的整数只有 四个。
怎么求 Euler 函数的值呢?我们当然可以暴力枚举 中所有的整数,找到其中与 互质的数的个数。枚举的复杂度为 ,判断是否互质的时间复杂度为 ,故时间复杂度为 。
但假如 , 的时间复杂度很明显会超时 QwQ。
我们要想一种更快的求 Euler 函数的算法,但在这之前,我们应该先了解一下素数唯一分解定理。
素数唯一分解定理:对于任意不小于 的正整数 ,都唯一存在一个严格递增的质数数列 ,以及一个正整数数列 ,使得:。
即:对任意一个不小于 的正整数,只有一种将其分解质因数的方法。
例如,,而且没有其他的分解方式了。
这个定理非常显然,但是要证明则需要使用数学分析相关的知识,故在此不进行证明。
我们只是需要利用这个定理来帮助我们求 Euler 函数值:假如将 分解质因数:,那么就有:
当然,因为证明步骤太过繁琐,此处依旧不对该结论进行证明,有兴趣的读者可以自行阅读其他有关材料。
但是这个公式启发我们:如果我们需要求一个数的 Euler 函数,只需要对它分解质因数就可以了,因为分解质因数的算法是 的,所以我们可以直接把 Euler 算法的时间复杂度优化到了 !QwQ
代码也很好写:
long long phi(long long n) { //求 Euler 函数
ll ans = n;
for(int i = 2; i <= n; ++i) {
if(n % i == 0) {
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
}
}
Euler 定理与扩展 Euler 定理
『CaO 你是在糊弄我们吗?一个破函数讲了半天,怎么优化快速幂还没告诉我们呢!』
别急~既然这个函数名字叫 Euler 函数,那么它肯定和那个大名鼎鼎的数学家 有关系。所以, 也给出了一个非常著名的定理,即 Euler 定理:
当 时,有 ,其中 是 Euler 函数。
当 为质数时,上述定理即退化为 Fermat 小定理。所以该定理又名 Euler-Fermat 定理。
证明如下(数学证明警告!):
我们不妨设从 中,与 互质的整数分别为 ,那么一定有:
所以有
两边同时消去 ,即得到 。
这也就是说,即使 中 不保证是质数,如果保证 ,也可以通过 Euler 定理算出来,时间复杂度为 。
然而并没有什么卵用我们可以轻松地构造出很多组 ,让 不互质。这个时候,Euler 定理的前提就不成立了,也就不能用 Euler 定理来加速原本的快速幂了。我们能否在 Euler 定理的基础上,得出一个更具有普适性的结论呢?
为了解决这样的问题,(据听说是)国内的某位 OIer,提出了扩展 Euler 定理:
对于任意的正整数 ,都有 。
当然,我们也就可以得出 时,。
证明如下(数学证明警告!):
不妨设 与 存在一个公共质因子 ,使得 ,其中 。
那么由 Euler 定理得
又因为 质因子 ,故有 ,即 。
故 。即 。
即 。
上述结论可表述为:若 ,则 。
不妨设 中含有素因子 ,且存在 。
则 。
即 。
对于 的每个素因子都成立,相乘即得:
这个定理给了我们一个启发:对于任意的 ,我们把普通的 Euler 定理和扩展 Euler 定理做一个整合,就可以得到这个公式:
所以,无论 再怎么大,最后需要计算的指数都可以被这个定理限制在 以内,这样,我们用快速幂计算的时间复杂度就不会超过 了!还不如预处理 Euler 函数的 的时间复杂度高。所以,项这样用扩展 Euler 定理加速的快速幂,又被戏称为光速幂。
代码
虽然在洛谷上,它是一道蓝题,但写起来真的是很轻松 QwQ~
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 100000001;
ll a, b, m, phim;
char B[MAXN];
bool flag = false; //判断 b 是否大于 phi(m)
ll phi(ll n) { //欧拉函数
ll ans = n;
for(int i = 2; i <= n; ++i) {
if(n % i == 0) {
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
}
return ans;
}
ll qpow(ll a, ll b, ll m) {
ll ans = 1;
while(b) {
if(b & 1) ans = (ans * a) % m;
b >>= 1, a = (a * a) % m;
}
return ans;
}
int main(void) {
cin >> a >> B >> m;
phim = phi(m);
for(int i = 0; B[i]; ++i) {
b = (b * 10) + B[i] - '0'; //类似于快读的输入方式,可以达到边读入边取模的目的
if(b >= phim) flag = true;
b %= phim;
}
if(flag) printf("%lld", qpow(a, b + phim, m)); //b > phi(m) 时需要加一个 phi(m)
else printf("%lld", qpow(a, b, m)); //否则直接上快速幂
return 0;
}
//by CaO
关于 Euler 函数前缀和
当然,Euler 函数的用途不止于此。考虑这样一个问题:求在 中互质的整数有多少对。
这个问题也就可以形式化地表示为,给定整数 ,求下面这个式子中 的值:
『这个我会! 暴力枚举 ,然后 判断是否互质,总复杂度为 !』
那么,当 时呢?
『让我想想……哦,我明白了!我们考虑下面的这个式子:
这个式子代表从 中,所有与 互质的数的个数。根据 Euler 函数的定义,这个式子就等于 ,所以我们对待求的式子做变形,就可以变为:
我们可以 处理出某个整数的 Euler 函数值,要求 个函数值,复杂度为 。』
那我再放大一点数据, 呢?
『我会杜教筛!还会 Möbius 反演,可快可快了!』
……不你不会╮( ̄▽ ̄)╭
『……为什么?』
不要老是拆我的台啊 kora(ノ=Д=)ノ┻━┻这两个我还不会呢!
『那……我会埃氏筛和 Euler 筛,这总行了吧……?』
OK,可以,很好!我们可以利用筛法来在 甚至 的时间复杂度来解决 Euler 函数的前缀和问题。
我们先了解 Euler 函数的一个性质:
Euler 函数是一个积性函数。对于一个定义在 上的函数 , 是积性函数,当且仅当 。
这也就是说:,都有 。
我们不妨考虑一个正整数 ,考虑将其分解质因数:,既然 是一个积性函数,又有 两两互质,所以一定有 。
考虑到 ,所以有 。
我们可以预先设定所有的 ,然后用线性筛或者埃氏筛,筛出所有的质数,对每个质数 ,将它的所有倍数都乘上 。这样,复杂度就被优化成了 或 了!
代码如下:
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 10000001;
ll n, phi[MAXN];
int main(void) {
scanf("%lld", &n);
for(int i = 1; i <= n; ++i) //初始化
phi[i] = i;
for(int i = 2; i <= n; ++i) //这里用了埃氏筛,因为线性筛太难写了 QwQ
if(phi[i] == i) //如果 phi[i] == i,就意味着这个数没有被任何一个质数筛过,那么它就也是一个质数
for(int j = i; j <= n; j += i) //埃氏筛模板
phi[j] = phi[j] * (n - 1) / n; //对每个 j 是 i 的倍数,更新 phi[j] 的值
//时间复杂度 O(n log log n),如果用线筛可以优化到 O(n)
for(int i = 1; i <= n; ++i) //线性求前缀和
phi[i] = phi[i - 1] + phi[i];
printf("%lld", phi[n]);
return 0;
}
//by CaO
Euler 函数的应用
正如前言所提到的,Euler 函数在数学中有着非常广泛的应用。例如:
BSGS 算法
BSGS 算法可以解一类名叫离散对数的同余方程,即形如 的方程。
根据 Euler 定理,当 的时候,。那么这个时候,我们就可以将 的范围缩小到 。这个时候,再利用分块的思想就可以将它的复杂度优化到 。
如果有机会,我会着重讲一下这个算法具体的实现。但是,在判断方程是否有解的时候,Euler 函数起到了非常重要的作用。
原根
对于两个正整数 和 ,假如 ,那么一定有 。这个时候,我们就可以把最小的满足 的最小的正整数 叫做 模 的阶,记作 。显然,,那么我们把满足 的整数 记作模 意义下的原根。
有了 Euler 函数,我们才能够定义原根的概念,有了原根,才有了像 NTT 这样的高性能的算法。所以,Euler 函数对解决算法问题的作用,不止是光速幂这一点。
总结
Euler 函数的作用其实不止上面这些,还涵盖了群论方面的知识点,所以如果你想更深入地学习数论和群论相关的知识,学习 Euler 函数和 Euler 定理是一件非常有用的事!
例题
本题目列表会持续更新。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】