在数论中,对正整数 n ,欧拉函数 φ(n) 是小于或等于 n 的正整数中与 n 互质的数的数目。
简言之从1 ~ n 取正整数,其中与 n 互质的数的个数称为欧拉函数,记作φ(n)
互质:两数除1以外没有别的公因数
例如:φ(8) = 4,因为1, 3, 5, 7均和 8 互质。
n = p1k1 p2k2 … prkr
这里 p1 p2指素数,任何一个数都可以表示成素数幂乘积的形式
同时所有的 piki 两两互质
最终得到:
计算方法的缺点在于当 n 是个巨大的整数时对其进行质因数分解非常困难,因此很难求出 n 的欧拉函数值φ(n), RSA加密算法中的安全性就基于大整数的素数分解问题的难解性,这也是该算法最主要的数学原理之一。
若p为质数,则φ( p ) = p - 1;
p为质数,与质数互质的数为1 ~ p-1,一共 p-1个。
任意一个大于1的正整数,从1到n中所有与n互质的数的和为[ n × φ(n) ] / 2;
根据更相减损术,因为gcd(n,x)= gcd(n,n - x),所以与n互质的数x,n - x成对出现,平均值为n / 2,和为[ n × φ(n) ] / 2。
若m,n互质,则φ(mn) = φ(m)× φ(n)
又称欧拉函数为积性函数。
结合欧拉函数的计算方法,因为m,n互质,所以m,n没有共同的因数,那么m*n的因数其实就是m的因数加上n的因数的总体,那么也就可以将φ(mn)拆分为φ(m)乘上φ(n)。
这个性质展现了一个数的欧拉函数值与其质因数欧拉函数值的关系,故可以利用筛法求出欧拉函数值。
应用以上性质可实现时间复杂度为 O( n \sqrt[]{n} n) 的情况下求得指定正整数的欧拉函数值。
由于公式中需要乘 n,所以初始化为这个数本身,而且除1外没有数的欧拉函数是本身。
首先初始化一个变量 res 为 n。然后,我们从 2 开始迭代判断每个可能因子 i 是否可以整除 n,直到 i 的平方大于 n。
如果 i 能整除 n,执行以下操作:
将 res 除以 i,并乘以 (i-1)。这是因为当 n 能被 i 整除时,n 中出现了一个与 i 互质的数,因此我们要减少与 n 互质的数的数量,并将结果更新到 res 中。
我们继续循环除以 i,直到 n 不再被 i 整除,这样可以避免重复计算同一个因子。
如果 n 大于 1,这意味着它是大于√n 的一个质数。在这种情况下,我们将 res 除以 n,并乘以 (n-1),以更新 res 的值。
最后,我们返回 res 作为欧拉函数的结果。
int phi(int n)
{
int res = n;
for(int i = 2; i * i <= n; i++)
{
if(n % i == 0)
{
res /= i;
res *= (i - 1);
while(n % i == 0)
n /= i;
}
}
if(n > 1)
{
res /= n;
res *= (n - 1);
}
return res;
}
由于具体要用到欧拉函数的题目往往并不简单求某个数的欧拉函数值,而是要进行计算量很大的操作,这时通过一般做法很难化简得到解法,我们往往通过打表来观察输出结果找规律。这时要用到一次性求出1 ~ n的欧拉函数求法。
可以让求欧拉函数与筛法结合,求 1~n的欧拉函数值。具体方法如下:
求1~n的欧拉函数值
int phi[1000010];
void init(int n)
{
phi[1]=1;
for(int i=2;i<=n;i++)
phi[i]=0;
for(int i=2;i<=n;i++)
{
if (!phi[i])
{
for (int j = i; j <= n; j += i)
{
if (!phi[j])
phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
}
}
}
}
可以保证范围内每个数都能被它的所有质因数筛且只筛一次。而且可以看出x是质数充要条件是φ(x) = x - 1,因此也顺便筛出了范围内所有的素数。时间复杂度为O(n logn logn),比一个一个求的 n* O( n \sqrt[]{n} n)更好。
也可以用一种更快的方法——欧拉筛
这种方式没有进行质因数分解,而是运用欧拉函数的性质,时间复杂度为O(n)。
void init(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!isnp[i])
primes.push_back(i), phi[i] = i - 1; // 性质,指数为1的情形
for (int p : primes)
{
if (p * i > n)
break;
isnp[p * i] = 1;
if (i % p == 0)
{
phi[p * i] = phi[i] * p; // 性质
break;
}
else
phi[p * i] = phi[p] * phi[i]; // 性质
}
}
}