欧拉函数_LVGreenary的博客-程序员宅基地

技术标签: 数论  算法理论  欧拉函数证明  欧拉函数算法  

基于同余定理和素筛的欧拉函数

在开始下面的欧拉函数学习之前,我希望各位大佬能够先熟练掌握素筛和基于同余定理的快速幂这两个算法,如果哪位大佬不太清楚这两个算法,可以先参考我博客中的《基于同余定理的快速幂》和《素数筛法》这两篇博客,熟悉之后,再来看这篇博客会有事半功倍的效果(๑• .* •๑)*


我们先说说欧拉函数是做什么的:

Euler函数:

欧拉函数是求小于x并且和x互质的数的个数(所谓两个数互质,那就是两个数的最大公约数是1)

通式:φ(x) = x ( 1 - 1/p1 )( 1 - 1/p2 )( 1 - 1/p3 )( 1 - 1/p4 )……( 1 - 1/pn )

*其中p1, p2……pn为x的所有不重合的质因数,x是不为0的整数(注意,我在这个地方加了一个不重合,比如 12 = 2 * 2 * 3 ,那么它的不重合的质因数就有 2 和 3,所以 φ(12) = x(1 - 1/2)( 1 - 1/3) = 4 ,也就是说 12 在 1~12 中有四个与其互质的数,分别为1、5、7、11,是不是2、3、4、6、8、9、10与12都不互质呢,很神奇吧 (ฅ>ω<ฅ))

有个小细节是:φ(1)=1(唯一和1互质的数就是1本身)


下面就给大家讲一些原理证明和一些推论,比较枯燥,但是都有助于你欧拉函数的理解和学习๑•ั็ω•็ั๑)

欧拉函数是积性函数:
1.在微积分学领域,积性函数指的是具有 f( ab ) = f( a )f( b )特性的函数,而在数论领域中,这个概念略有不同(仅定义在正整数上),它揭示了整数的很多性质。

为了区分通常意义上的函数,我们定义算数函数(定义在所有正整数上的函数称为算数函数):
在整个积性函数篇里,不加说明地,我们总是讨论算数函数。

定义1.1 算术函数 f (x)如果满足对于任意两个互质的正整数 m 和 n,均有f(mn) = f(m)f(n),就称f(x)为积性函数(或乘性函数)。如果对于任意两个正整数 m 和 n ,均有f(mn) = f(m)f(n),就称为完全积性函数。其实很容易找到一些积性函数,如f(n) = 1 , f(n) = n , f(n) = n^2,事实上所有的幂函数都是积性函数。
2.结合积性函数的定义和算数基本定理,很容易得到下面的定理:
定理2.1 如果f(x)是一个积性函数,对于任意的正整数 n 有素数幂分解n=( p1^a1 )*( p2^a2 )…( ps^as ),那么有f( n ) = f(p1a1)f(p2a2)…f(ps^as)

所以说:设m与n是互素的正整数,那么f( mn ) = f( m )f( n )


OK通过前面的小引子,下面向您隆重推出今天的主角——欧拉函数(也被称为欧拉ϕ函数)
下面我们就来探讨欧拉函数在各个点上的取值。很容易得到对于素数p,ϕ( p ) = p − 1 ,那么反过来是不是也成立呢?

**定理1.1:**如果 p 是素数,那么ϕ( p ) = p − 1 .反之,如果p是正整数且满足ϕ( p ) = p − 1,那么p是一个素数。
证明:前句可由互质定义得到,我们只证明后句。若p不是素数,那么或是1或是合数。而ϕ( 1 ) = 1,但若 p 是合数,得有因子 0 < d < p0 < d < p ,而 p 不与自身互质,这使得和 p 互质的数至多只有p−2个,所以也不可能,故 p 是素数。
我们再进一步,看欧拉函数在素数的幂下的取值。实际上和素数幂 p^n 不互质的只有 p 的倍数,一共有pn/p=p(n−1)个,故ϕ( p^n )=pn−p( n−1 )。

定理1.2: 如果 p 是素数,那么ϕ(p^n) = pn−p( n−1 )。

接下来我们讨论更一般的情况,为此,我们需要证明欧拉函数是一个积性函数。定理的证明依赖于同余的一些简单性质,由于本文是本系列的第一篇,所以这里尽量把需要用到的性质先给证一证。如果你对这些性质比较清楚,可以直接跳过下面的补充部分,直接进入定理的证明。

**定义1.3:**一个模 m 完全剩余系是一个整数的集合,使得任意整数恰和此集合中的一个元素模 m 同余。
引理1.3: m 个模 m 不同余的整数的集合构成一个模 m 的完全剩余系。
1.3.1剩余类的定义:
一个整数被正整数n除后,余数有n种情形:0,1,2,3,…,n-1,它们彼此对模n不同余。这表明,每个整数恰与这n个整数中某一个对模n同余。这样一来,按模n是否同余对整数集进行分类,
可以将整数集分成n个两两不相交的子集。我们把(所有)对模n同余的整数构成的一个集合叫做模n的一个剩余类
1.3.2完全剩余系的定义:
从模n的每个剩余类中各取一个数,得到一个由n个数组成的集合,叫做模 n ( 就是用 n 对某个数求余 )的一个完全剩余系
证明: 假设 m 个模 m 不同余的整数的集合不是一个模 m 的完全剩余系。那么可能存在整数 a,使得集合中存在不止一个元素和 a 同余,但由于同余的性质,如果 a 和 b 模 m 同余,且 a 和 c 模 m 同余,那么必有 b 和 c 模 m 同余,和假设不符;那么可能存在整数 a,使得集合中不存在元素和 a 同余,但模 m 的余数只有 m 种可能, a 否定了一种可能,意味着集合里的元素模 m 至多只有 m−1 个值,但集合中的 m 个元素互不同余,应有 m 个不同的值,和假设不符。故原假设成立。
由上述引理,容易得到 0 , 1 , 2 , 3 , … , m−1 是模 m 的一个完全剩余系。

**定理1.4:**当n为奇数时,有:ϕ(2n) = n,因为2n是偶数,偶数与偶数一定不互素,所以只考虑2n与小于它的奇数互素的情况,则恰好就等于n的欧拉函数值。但是你不能说

引理1.5: 若 r1 , r2 , … , rm 是模 m 的一个完全剩余系,且正整数 a 满足gcd( a,m ) = 1 ,则对任意整数b,ar * 1+b , ar * 2+b , … , ar * m+b 都是模 m 的完全剩余系。
证明:由引理1.3,我们只需证明这 m 个数互不同余。取任意两个元素 ri , rj , i≠j 相减,得a * ri+b−(a * rj+b)=a( ri − rj ) .
若我们有a * ri≡a * rj(mod m) , 基于同余的性质,在gcd(a,m)=1的情况下,两边可以约去 a,于是得到 ri≡rj(mod m),这和假设矛盾,故a * ri 与 a * rj 不同余,故引理成立。

结合以上定理,以及算数基本定理,我们可以求得任意正整数的欧拉函数值:

定理1.6: 设n=(p1a1)*(p2a2)…(pk^ak)为正整数 n 的素数幂分解,那么ϕ(n) = n( 1−1/p1 )( 1−1/p2 )…( 1−1/pk )
**注意:n = (p1a1)*(p2a2)…(pk^ak) **
而:ϕ(pkak)=(pkak)−(pkak−1)=(pkak)(1−1/pk)
**证明:**因为ϕ( n )=ϕ(p1a1)ϕ(p2a2)…ϕ(pkak),又因为ϕ(pkak)=(pkak)−(pkak−1),于是有:
ϕ(n)=(p1a1)*(p2a2)…(pk^ak)(1−1/p1)(1−1/p2)…(1−1/pk)=n(1−1/p1)(1−1/p2)…(1−1/pk)

所以计算单个欧拉函数值的过程实际上就是先对正整数 n 进行分解的过程,我们从2开始从小到大寻找 n 的因子,显然找到的最小的因子必是素数(如果是合数,那在这之前就应该能找到这个合数的因子,当然也是 n 因子,肯定也是素数),找到后把这个素因子全部约去,并计算(1−1/p1),然后接着向前,由于之前的因子已经消去,我们仍能保证找到的因子是素数。

完整的代码如下:

#include<iostream>
#include<cmath>
using namespace std; 
//下面是欧拉函数的核心代码,在欧拉函数之前可以写素筛的函数,以便在欧拉函数的第一层循环中直接对素数遍历,非素数不用遍历 
int euler(int x){
	int res = x;
	for(int i = 2; i<(int)sqrt(x*1.0)+1;i++)	//该for循环中的判断语句,与素筛中第一层循环的处理方法相同,即根号n~n中的是由1~根号n中的数的倍数,或者是大于根号n的素数,比如n =  2 * 97 * 101 根号n肯定大于97,那101又如何取到呢?其实在for循环中把1~根号n的判断完后剩下的101会在下面的	if(x > 1) res = res /x * (x -1);中被处理,因为如果剩余的数大于1,那么肯定是大于根号n的素数,且只有这一个大于根号n的素数其余的大于根号n的数均为合数 
		if(x % i == 0){
			res = res / i * (i - 1);			//这步相当于res = res * (i - 1) / i 
				while(x % i == 0) x /= i;		//这一步就是所谓的将x中重合的相同质因子全部舍掉,因为欧拉函数通式中重合的质因子只需要一个 
		}
	if(x > 1) res = res /x * (x -1);
	return res;
}
int main()
{
	int n;
	while(cin>>n){
		cout<<euler(n)<<endl;
	}
	return 0;
}

上面的代码在判断很少的几个数的欧拉函数值的时候可以使用,那如果让你求出1~10000所有的数的欧拉函数值那该怎么办呢?
这个时候我们就要借助打表的思想,即像素筛那样将每个数的欧拉函数值存放到对应下标的数组中去,但是用递推法求欧拉函数的值有几点需要注意的地方,笔者已经在代码中注释了,还望读者耐心阅读,并自己动手去多调试调试:

#include<iostream>
#include<cmath>
using namespace std; 
int p[100010];
void phi(){
	for(int i = 1;i<100010;i++) p[i] = i;//假设刚开始每个数的欧拉函数值等于它本身,这一步操作为下面素数的判定打下了良好的基础 
	for(int i = 2;i<100010;i+=2) p[i] >>=1; //这个地方用到了定理1.4的知识点,将所有的偶数都除以2,而且用的位运算,那么有读者会说了,8和4的欧拉函数值怎么可能一样,其实这里除以2就相当于n(1-1/2),是不是相当于n/2 ? 因为偶数都有2这个质因子 ,所以索性直接除以2 
	for(int i = 3;i<100010;i+=2){
		if(p[i] == i){					//请看代码末
			for(int j = i;j<100010;j+=i) p[j] = p[j] - p[j]/i;//其实就是 p[j] = p[j]*(1-1/i),这个地方不写成p[j] = p[j]*(1-1/i) 的原因是(1 - 1/i)会变成浮点数,使结果不精确 
		}
	} 
}
int main()
{
	phi(); 
	int n;
	while(cin>>n){
		cout<<p[n]<<endl;
	}
	return 0;
}
//因为解释太长我就注释到这了,代码中if(p[i] == i)这一步是用来判断 i 是不是素数,首先i肯定是奇数,然后如果p[i]依旧等于i,说明 p[i]在之前的循环中值没有改变,比如i = 3的时候,3的倍数所对应的下标的值都会计算p[3的倍数] = p[3的倍数]*(1 - 1/3),所以在循环到 i=9 时,p[9]的值已经被改变,所以没被改变的,都是除了1和它本身没有因子的,那么肯定是素数,有人可能想问i一直遍历奇数,那么偶数呢?偶数首先肯定不是素数,并且在下面第二层遍历的时候,读者应该能够发现当i = 3时,遍历到了j = 6,所以依旧进行了计算p[6] = p[6]*( 1 - 1/3),并且后面那个p[6]的值已经在前面除以2了,是不是很神奇啊 

大佬们不懂的地方要私信我呀(๑• . •๑),喜欢就顶一下吧,嘻嘻(๑• . •๑)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sand8o8time/article/details/76781541

智能推荐

Catalan数(卡塔兰数)的证明_catalan数证明_ccx20060313的博客-程序员宅基地

所以我们想到:定义p(l)为任意一条路径l在y=x下方所行走的路径的长度和。其次想到化归。我们证明任意一条满足p(l)=2k的路径l都可以化归为满足p(l')=2k+2的l'(2k-2同理)如图,黑色箭头为调整箭头(将l划分为若干个箭头,第一个向右且恰好碰到y=x的箭头),记为B剩下的部分,B前面的所有箭头记为A(红色部分),后面的记为C(绿色部分)则路径l可记为A-&gt;B-&gt;C将l'定义为C-&gt;B-&gt;A即可(右图)于是我们将左图化归到了右图此时p(l')=p(l)+2

Bugku:加密 +[]-_酥酥糖学习的博客-程序员宅基地

​这道题和上一道题是非常类似的,奇怪的东西。但是我感觉都免疫了,因为,依然虽然是另一种也一样啊!!flag{bugku_jiami_23}欢迎关注我的微信公众号!!~~一起快落学习CTF吧!!~~(*^▽^*)人无远虑,必有近忧。...

计算机组成原理——第四章测试题 中 (1)_zhandsomeboy的博客-程序员宅基地

1单选(1分)设由四个模块组成的四体存储器结构,每个体的存储字长为16位,存取周期为250ns,假设数据总线宽度为16位,总线传输周期为50ns,试求顺序存储和交叉存储的带宽分别为_ B__ bps A.1.610^8 和 6.410^7B.6.410^7 和 1.610^8C.6.410^8 和 1.610^8D.6.410^7 和 1.610^72单选(1分)按配奇原则配置1...

CCF201803-1跳一跳(C语言)_全幼儿园最聪明的博客-程序员宅基地

题目问题描述  近来,跳一跳这款小游戏风靡全国,受到不少玩家的喜爱。  简化后的跳一跳规则如下:玩家每次从当前方块跳到下一个方块,如果没有跳到下一个方块上则游戏结束。  如果跳到了方块上,但没有跳到方块的中心则获得1分;跳到方块中心时,若上一次的得分为1分或这是本局游戏的第一次跳跃则此次得分为2分,否则此次得分比上一次得分多两分(即连续跳到方块中心时,总得分将+2,+4,+6,+8…)。...

Android Binder机制浅析_binder机制对cts的影响_xxiang1x的博客-程序员宅基地

转载自:http://blog.csdn.net/singwhatiwanna/article/details/19756201摘要Binder是android中一个很重要且很复杂的概念,它在系统的整体运作中发挥着极其重要的作用,不过本文并不打算从深层次分析Binder机制,有两点原因:1是目前网上已经有2篇很好的文章了,2是对Binder机制进行深入底层乃至驱动的分析这一过程

2019.1.22写了五个小时的《C Primer Plus》第七章编程练习答案_Miaplacidus的博客-程序员宅基地

下午本来说要陪一个同学回高中宣讲,但他的学校通知他延后,于是下午两点开始写第七章课后题……晚上17:26才调试完成……各位看出什么BUG一定要在下面留言啊7.12.1编写一个程序读取输入,读到#字符停止,然后报告读取空格数,换行符数目以及所有的其它字符数目。#include&amp;amp;amp;lt;stdio.h&amp;amp;amp;gt;int main(void){ char input; int space=0...

随便推点

Linux builtin 命令_weixin_42098295的博客-程序员宅基地

Linux命令是对Linux系统进行管理的命令。对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心,与之前的DOS命令类似。linux命令在系统中有两种类型:内置Shell命令和Linux命令。本文主要介绍Linux builtin 命令。原文地址:Linux builtin 命令...

Spring Boot JPA分页_苏叶新城的博客-程序员宅基地

常用方法总记录数 :page.getTotalElements() 当前第几页:page.getNumber() 总页数:page.getTotalPages()当前页面的List:page.getContent()当前页面的记录数:page.getNumberOfElements()基本分页单元public class Meta { private int page; /...

ibm cognos 10 for linux安装步骤_linux安装cognos_Hero-feng的博客-程序员宅基地

安装准备安装环境:redhat6.2所需安装的文件:cognos BI server 10.1cognos transformer10.1oracle 10g客户端apache(可选)openldap-server 2.4openldap-client 2.4openldap-devel 2.4jdk -1.6-64位操作系统必须安装中文字库硬件要求:内存:2G及以上CPU:2.2G HZ双核及以...

ESP8266开发之旅 小程序之阿里云篇② “IOT菜鸟”小程序,源码分析,创作自己的小程序_阿里云 esp8266 微信小程序_单片机菜鸟哥的博客-程序员宅基地

文章目录1.前言2. 代码结构![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503233343818.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RwamNuMTk5MA==,size_16...

2345电脑管家_极限挑战:同时安装4大国产杀毒软件,我的电脑是最安全的?_weixin_39834281的博客-程序员宅基地

还没到国庆假期,老毛桃就提前给自己放了假,闲着就作妖,这不?现在就忙着卸载。人固有一秃,或秃于科研,或秃于卸载!说到作妖,是怎么一回事呢?此前不少网友私信让老毛桃挑战一下同时安装360和电脑管家。对此,为了满足大家如此“无理”的要求,老毛桃决定把难度升级,同时安装了4大国产杀毒软件!听起来很了不起的样子,“四大天王”围绕着老毛桃的电脑转,看似无比安全,其实不然,详细的作妖过程请接着往下看!首先,老...

推荐文章

热门文章

相关标签