数论 —— 欧拉函数

数论 —— 欧拉函数

  • 数论 —— 欧拉函数
    • 什么是欧拉函数?
    • 欧拉函数计算方法
    • 欧拉函数的常用性质
    • 代码求解欧拉函数
    • 写题时更实用更常见的做法——打表


什么是欧拉函数?

在数论中,对正整数 n ,欧拉函数 φ(n) 是小于或等于 n 的正整数中与 n 互质的数的数目。

简言之从1 ~ n 取正整数,其中与 n 互质的数的个数称为欧拉函数,记作φ(n)

互质:两数除1以外没有别的公因数

例如:φ(8) = 4,因为1, 3, 5, 7均和 8 互质。


欧拉函数计算方法

  1. 把 n 表示成素数的乘积,即进行质因数分解

n = p1k1 p2k2 … prkr
这里 p1 p2指素数,任何一个数都可以表示成素数幂乘积的形式
同时所有的 piki 两两互质

  1. 利用性质对上式进行变形

最终得到:
数论 —— 欧拉函数_第1张图片
计算方法的缺点在于当 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. 利用phi数组存欧拉函数值。将phi数组初始化为0,phi[1]的值为1。
  2. 从2开始循环每个数处理的同时处理其所有的倍数,让他们么每次被处理都乘上(1 - 1 / i),最终所有的数都会被其因数处理一次。
  3. 第一个 if( !phi[j] ) 是为了只让因数去对它的倍数进行操作:一个数如果是合数,它必然被之前的素数处理过,所以phi的值不可能是0,这样就保证了只有素数才会对其倍数进行处理。
  4. 第二个if( !phi[j] ) 表示公式中的乘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]; // 性质
        }
    }
}

你可能感兴趣的:(算法竞赛,算法)