欧拉函数
意义
对于正整数\(n\),\(\varphi(n)\)表示小于等于\(n\)的正整数中与\(n\)互素的数的数目
定义
\(\varphi(n)=n\cdot(1-\frac 1{p_1})\cdot(1-\frac 1{p_2})\cdot...\cdot(1-\frac 1{p_n})\)
其中\(p_i\)表示\(i\)的质因数
特别地,\(\varphi(1)=1\)
证明
用类似于容斥原理的方法求\(1\sim n-1\)中与\(n\)互素的数:
先把\(n\)的所有质因数\(p_i\)的倍数都筛掉,再把\(p_i,p_{i+1}\)的公共倍数添加回来,再去掉\(p_i,p_{i+1},p_{i+2}\)的公共倍数...
得到\(\varphi(n)=n-\frac n{p_1}-\frac n{p_2}-\cdots+\frac n{p_1p_2}+\frac n{p_2p_3}+\cdots\)
由相关数学知识化简得到定义式。
性质
\(a,b\)互素时由定义式易证\(\varphi(a\cdot b)=\varphi(a)\cdot\varphi(b)\),由此欧拉函数是积性函数。
求解方法
-
由定义式可以得到一种直白的求法:(在线算法)
用唯一分解定理的方法找出\(n\)的所有质因数,同时用定义式求即可
由于\(p_i\)是\(n\) 不同的质因数,所以计算中除法不会出现不能整除的情况
为了防止爆精度,计算时先除后乘。
int phi(int n) { if(n == 1) return 1; int p = n; for(int i=2, n1=n; i*i<=n1; i++) if(n%i == 0){ p = p / i * (i-1); while(n%i == 0) n /= i; } if(n>1) p = p / n * (n-1); return p; }
-
打欧拉函数表:(离线算法)用筛法,边筛素数边算
-
埃氏筛
初始化\(phi[i]=i\)
筛到素数\(p\),在标记\(p\)的倍数不是素数的同时计算,\(phi[i*p]=phi[i*p]/p*(p-1)\)
void sieve(int n) { phi[1] = 1; for(int i=2; i<=n; i++) { if(!npr[i]) continue; for(int j=2; i*j<=n; j++) { npr[i*j] = 1; phi[i*j] = phi[i*j] / i * (i-1); } } }
-
欧拉筛(线性筛)
因为欧拉筛中每个数只筛一次,所以要一次算出最终结果。
设\(p\)为素数,分类讨论如下:
- \(phi[p]=p-1\)
- 已知\(phi[x]\)且\(p\)能整除\(x\):\(phi[x*p]=phi[x]*p\)
- 已知\(phi[x]\)且\(p\)不能整除\(x\):\(phi[x*p]=phi[x]*(p-1)\)
每一个对应的简单证明:
- \(p\)以内所有数都与\(p\)互质,所以答案为\(p-1\)
- \(p\)不是\(x*p\)新增的素数,故由定义式知\(phi[x]\)算\(phi[x*p]\)时后边带括号的部分相同,只是前面的\(x\)变成了\(x*p\),所以\(phi[x]*p\)即可
- \(p\)是\(x*p\)新增的素数,由\(phi[x]\)算\(phi[x*p]\)时要乘一个因子\(\frac{p-1}p\),同时前面\(x\)变成\(x*p\),约分后为\(phi[x]*(p-1)\)
void sieve(int n) { np = 0; phi[1] = 1; for(int i=2; i<=n; i++) { if(!npr[i]) p[++np] = i, phi[i] = i-1; for(int j=1; j<=np && i*p[j]<=n; j++) { npr[i*p[j]] = 1; phi[i*p[j]] = phi[i] * (i%p[j] ? p[j]-1 : p[j]); if(i%p[j] == 0) break; } } }
-