目录
前言
欧拉函数
欧拉函数通项
代码
欧拉筛
代码
欧拉定理
逆元
快速幂
总结
前言
今天的内容是欧拉函数,欧拉定理,费马小定理,快速幂和快速幂求逆元。
欧拉函数
先说定义:
表示从1 ~ n
中和n
互质的数的个数。(后面我会用phi(n)
来表示,大家知道这个是欧拉函数即可。)
欧拉函数通项
根据算数基本定理:
任何一个非1
的自然数都可以唯一的拆解成这个形式。
随后根据这个算数基本定理再根据一系列的合并,计算,就可以得到phi(n)
的通项公式,即:
来大致的讲一下这个公式是怎么推导出来的(可以跳过)。
首先,我们若想直接求出phi(n)
显然是不可能的,所以我们使用间接法,筛掉从1 ~ n
中所有n
的约数,剩下的数字就是质数了,那么根据容斥原理,筛掉所有的约数的过程就变成了这样。
-
减去所有
p_i
的约数。 -
加上所有
p_i和p_j
的约数。 -
减去所有
p_i和p_j和p_k
的约数。 -
以此类推。
不明白为什么这样做的小伙伴建议先去了解一下容斥原理。是一个很简单但是很好用的定理,我们的前缀和公式的推导就用到了容斥原理。
随后将公式进行变形合并就变成了phi(n)
的样子(也可以简单的理解为多项式定理)。
代码
int euler(int x)
{int n = x;for (int i = 2; i <= x / i; i += (i & 1) + 1) //跳过偶数优化,可加可不加{if (x % i == 0){n = n / i * (i - 1);while (x % i == 0)x /= i;}}if (x > 1)n = n / x * (x - 1); //注意这里先做乘法再做除法防止溢出return n;
}
欧拉筛
依旧是基于算数基本定理。是在我们的线性筛法的基础上改进的算法,先来复习一下线性筛法。
基本原理:所有合数都由其最小的质因数筛掉,代码:
bool st[N];
void eulerShai(int n)
{vector<int> prime;for (int i = 2; i <= n; i++) //因为要筛到每个数,所以要到n{if (!st[i])prime.push_back(i);for (int j = 0; prime[j] <= n / i; j++){st[i * prime[j]] = true;if (i % prime[j] == 0)break;}}
}
随后我们来思考如何通过线性筛法来推导出欧拉筛法。
如果数字x
是质数,那么从1~x-1
中所有的数字都与x
互质,则phi(x) = x - 1
prime[j]
是i * prime[j]
的最小质因子,根据欧拉函数的公式欧拉函数的值只与i * prime[j]
和它的质因子有关,那么就分为了两种情况。
-
i
中包含prime[j]
,已知phi(i)
,显然质因子部分不会有变化,只需要在原本的基础上改变n
即可,结果为:prime[j] * phi[i]
-
i
中不包含prime[j]
,和前面同理,我们需要改变一下n
但同时也需要再乘上一个质因子的部分1 - 1/prime[j]
,最中化简为phi[i] * (prime[j] - 1)
(建议大家自己手推一边,光看的话真的……)
最终,我们就得出了欧拉筛法的代码,伪代码如下:
if(x是质数)phi[i] = i - 1; for(小于等于x的所有质数j) {if(i包含j) phi[i * j] = phi[i] * (j - 1);else(i不包含j) phi[i * j] = phi[j] * j; }
代码
void eulerShai(int n)
{vector<int> prime;varphi[1] = 1;for (int i = 2; i <= n; i++) //因为要筛到每个数,所以要到n{if (!st[i]){//printf("%d ", i);prime.push_back(i);varphi[i] = i - 1;}for (int j = 0; prime[j] <= n / i; j++){//printf("%d ", i * prime[j]);st[i * prime[j]] = true;if (i % prime[j] == 0){varphi[i * prime[j]] = varphi[i] * prime[j];break;}varphi[i * prime[j]] = varphi[i] * (prime[j] - 1);}}
}
欧拉定理
讲完了欧拉函数我们来讲一些欧拉函数的应用。
欧拉定理,定义是:
若a
和n
互质则:
特别的,若n
是质数,则
这就是我们的费马小定理(就是将质数的phi
代入)
逆元
而欧拉定理的一大功能就是求模意义下的逆元了(注意是模意义下),那么什么是逆元呢?
和线性代数中的逆元一样,如何a/b
可以表示成a*x
的形式,那么x
就是b
的逆元,表示为
。
可得公式:
左右两边同乘b
:
消掉a
所以我们若想求b
的逆元其实就是求b * x = 1
根据我们的欧拉定理
这里提一嘴,只有再a
与n
互质的情况下才会存在a
的逆元,而为了保证集合的完备性,我们一般只会考虑n
为质数的情况,那么由费马小定理。
提出一个a
所以我们若想求逆元其实就是要求a
的n-2
次幂(模n意义下)。
因为考虑到有的小伙伴可能没有学过快速幂算法,所以主播在这里也再讲一下。
快速幂
我们正常求幂次方的算法是累乘法,这显然太慢了。
先列出a的k次幂
为了方便讨论我们设k
为5
,随后根据二进制表示,可以将k拆分,式子为:
将指数部分拆开
所以我们只需要预处理出这两项就可以算出a
的5
次幂,思路很简单,接下来是代码。
int quick_pow(int a, int k, int p)
{int l = 1;a %= p;while (k){if (k & 1) l = (l * a) % p;k >>= 1;a = (a * a) % p;}return l;
}
代码很精巧,建议大家多花点时间理解一下。
总结
数论这部分就是这样的,对于初学者来说很难理解(主播也是学了三遍才算是能够讲出来),但是自己手推一遍的话会极大的加快理解速度。
在这里主播诚心的建议大家不要空想,一定要根据思路自己推出来,推出来只后你会发现数论部分真的很简单。