前情提示:如果不了解分解质因数算法和欧拉筛质数的算法,强烈建议先看看我之前的文章 质数章-数论代码笔记(超级细节)。否则可能会比较难理解。
在介绍欧拉函数之前,我想给大家一个初始印象,把欧拉函数当成一个类似于“质数”的定义,同样的,类似于质数,我们有判断一个数是不是质数,筛一个范围内的质数的方法,我们也有判断一个 数是不是欧拉函数,计算一个范围内的欧拉函数的方法。
好了,让我们进入正题。
1. 欧拉函数
首先给出欧拉函数的定义:
如果为了方便,可以直接记忆然后去看欧拉函数筛,如果想更进一步了解,且听我分析分析。
我们想直接求与N互质的数比较难(调用gcd函数)。所以我想从反方向入手,先找出与N不是互质的数。由算数基本定理我们知道,一个整数一定能分解成质数的乘积。那么我们可以知道如果1~N中有一个数是这些质数的倍数,那么它一定不与N互质。
不妨以为例,1~N中为
的倍数的个数,我们可以直接用
算出来,那么
的倍数是
。
直接把这些数相加吗?很显然不是,拿6举例子,它会被2,3给标记。所以我们要用到集合论中的容斥原理。
话说我对容斥原理的理解就是+,-,+,-
如果你对数学不感兴趣,请和我继续下去,我不会介绍容斥原理(虽然可以从韦恩图中推出来这个公式)
最后化简并提取我们能得到
你可能会比较奇怪,为什么要写成这个形式我写成这样不行吗?
答案是,为了方便计算机计算。。。。。
下面看代码:
int phi(int x)
{
int res = x;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
首先是分解质因数(不会的自觉去找资料),然后边分解边累计结果。时间复杂度是分解质因数的时间复杂度,为。
注意啊,有乘法有除法,先除后乘防止溢出,类似于有加法有减法,先减后加。
Tips: 第二种思路推公式:
首先有一点:
- 欧拉函数是一个积性函数,如果
互质,那么
。这个性质很重要啊,一会的欧拉函数筛也要用到这个性质。
我从网络上找了张图,很容易理解。
2. 欧拉函数筛
类似于质数,我们求出了一个数的欧拉函数,那有没有一种办法,我能求出1~N的欧拉函数呢。
有,就是使用欧拉函数筛。
先给出一个公式:
为什么会有这个公式?
首先我们知道当i 与 p(p是质数)互质时,就有 ,由上面提到的欧拉函数的积性我们知道,
。
那当不互质的时候呢?我们有已经包含了i*p的所有因子,所以
。
看代码,与线性筛框架基本一模一样。每次都用最小的质因子去计算出对应的欧拉函数。
如果i是质数,它会直接赋值为i-1,如果i是合数,在遍历到它之前,它就会被筛掉(计算出值)。
int primes[N], cnt; // primes[]存储所有素数
int euler[N]; // 存储每个数的欧拉函数
bool st[N]; // st[x]存储x是否被筛掉
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}