欧拉函数笔记
首先,我们先来了解一下欧拉函数的定义
定义
- 在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目.
- 一组数 \({x_1,x_2,x_3,…,x_s}\) 称为是模 \({m}\) 的既约剩余系(简称缩系),如果对 \({\forall j,s \in \mathbb Z,1 \le j \le s,(x_j,m)=1}\) ,并定义 \({\phi (m)=s}\) ,即 \({\{1,2,…,m\}}\) 中与 \({m}\) 互素的数的个数,称为欧拉函数.
- 特别地, \({\phi(1)=1}\) .
- 而对于 \({m>1}\) , \({\phi(m)}\) 就是 \({1,2,…,m-1}\) 中与 \({m}\) 互素的数的个数.
- 对于 \({p \in \{ }\) 素数 \({ \} }\) ,显然有 \({\phi(p)=p-1}\) .
好,相信这些大家都能理解。(大概)
不懂也没事,
接着往下看……
通式
1.对于一个正整数 \({n}\) ,分解质因子得 \({n = p_1^{x_1} p_2^{x_2} … p_k^{x_k} }\) ,有 \({ \phi(n) = n ( 1 - \frac{1}{p_1} ) ( 1 - \frac{1}{p_2} ) … (1 - \frac{1} {p_k}) }\)
证明
首先, \({ \phi (n)}\) 表示的是 \({n}\) 以内和 \({n}\) 互质的数,对吧?
总共有 \({n}\) 个数,减去和 \({n}\) 不互质的,剩下的不就是互质的个数了吗?
不互质的好像也挺好求,
有 \({n}\) 的质因子的数不就和 \({n}\) 不互质吗?
所以在 \({n}\) 以内找出是 \({n}\) 质因子的倍数的个数
也就是: \({ \frac{n} {p_1} + \frac{n} {p_2} + … \frac{n}{p_k}}\) 对吗?
……
不对!
可以发现可能会有一些数被重复算了,比如:6 就同时是 2,3 的倍数。
所以我们还要把多出来的次数减去…
也就是容斥原理
最后变式可得到原式.
Code:
long long getPhi(int n) {
long long ans=n;
for(int i=2;i*i<=n;i++)
if(n%i==0) {
ans=ans*(i-1)/i;
while(n%i==0)
n/=i;
}
if(n>1) ans=ans*(n-1)/n;
return ans;
}
欧拉函数与线性筛
然而,在实际中要使用很多个欧拉函数的时候
我们也不可能一个个用这个通式求吧?
时间复杂度太大了。
有个东西叫线性筛……
怎么推?
要知道,欧拉函数还是积性函数.
对于, \({gcd(m,n)=1}\) , 有 \({\phi(mn)=\phi(m)*\phi(n)}\) .
它是可以一路推过去的。
那么对于一个质数 \({p}\) ,和一个正整数 \({n}\) ,有两种情况:
1. \({p}\) 与 \({n}\) 互质:
显然可以直接推: \({\phi(pn)=\phi(n)*\phi(p)}\) 或 \({\phi(n)*(p-1)}\)
2. \({p}\) 与 \({n}\) 不互质:
因为 \({p \in }\) 质数,所以 \({p}\) 就是 \({n}\) 的一个质因子.
所以 \({pn}\) 的质因子和 \({n}\) 是完全一样的,
所以由欧拉函数通式: \({ \phi(n) = n ( 1 - \frac{1}{p_1} ) ( 1 - \frac{1}{p_2} ) … (1 - \frac{1} {p_k}) }\),就有 \({\phi(pn)=pn( 1 - \frac{1}{p_1} ) ( 1 - \frac{1}{p_2} ) … (1 - \frac{1} {p_k})=p *\phi(n)}\).
利用以上的式子,就可以用线性筛算出 \({1}\) 至 \({n}\) 的欧拉函数。而时间复杂度只有 \({O(n)}\) !
Code:
void ini() {
phi[1]=1;
memset(isPri,true,sizeof(isPri));
isPri[1]=false;
for(int i=2;i<=n;i++) {
if(isPri[i]) {
phi[i]=i-1;
pri.push_back(i);
}
for(int j=0;j<pri.size();j++) {
int x=pri[j]*i;
if(x>n) break;
isPri[x]=false;
if(i%pri[j]!=0) phi[x]=phi[i]*phi[pri[j]];
else {
phi[x]=phi[i]*pri[j];
break;
}
}
}
return ;
}
定理
- \({\sum \limits_{d|n} \phi(d) = n}\)
证明1:
我们设一个集合为 \({ \{ \frac{1}{n} , \frac{2}{n} , \frac{3}{n} , … , \frac{n}{n} \} }\)
对于上述集合的每个元素,将其化成最简形式。
设一个最简分数为 \({ \frac{a}{b} }\) ,
则一定有: \({b|n , (a,b)=1 , a \le b }\)
由第一个式子可知 \({b}\) 就等价于定理式子里的每一个 \({d}\)。
再由第二、三个式子可得,每个 \({b}\) 对应的 \({a}\) 有 \({\phi(b)}\) 个。
而它们的总和,也就是集合的元素个数 \({:n}\).
代入 \({b=d}\) ,就化成了原式 \({=n}\).
得证。
证明2:
我们以枚举质因子的方式,将 \({d}\) 变换形式可得下述.
\({\sum \limits_{d|n} \phi(d) = \sum \limits^{a_1}_{i_1=0} \sum \limits^{a_2}_{i_2=0} … \sum \limits^{a_k}_{i_k=0} \phi(p_{1}^{i_1} p_{2}^{i_2} … p_{k}^{i_k} ) }\)
\({\ \ \ \ \ \ \ \ \ \ \ \ \ }\) \({=\sum \limits^{a_1}_{i_1=0} \sum \limits^{a_2}_{i_2=0} … \sum \limits^{a_k}_{i_k=0} [ \phi (p_1^{i_1} p_2^{i_2}…)\times \phi(p_k^{i_k})] }\)
\({\ \ \ \ \ \ \ \ \ \ \ \ \ }\) \({=\sum \limits^{a_1}_{i_1=0} \sum \limits^{a_2}_{i_2=0} … [\phi(p_1^{i_1}p_2^{i_2}…) \times \sum \limits_{i_k=0}^{a_k} \phi (p_k^{i_k})]}\)
\({\ \ \ \ \ \ \ \ \ \ \ \ \ }\) \({=\sum \limits^{a_1}_{i_1=0} \sum \limits^{a_2}_{i_2=0} … [\phi(p_1^{i_1}p_2^{i_2}…) \times p_k^{ak}]}\)
\({\ \ \ \ \ \ \ \ \ \ \ \ \ }\) \({=p_k^{ak}\sum \limits^{a_1}_{i_1=0} \sum \limits^{a_2}_{i_2=0} … [\phi(p_1^{i_1}p_2^{i_2}…) }\)
重复以上过程,最后可得:
\({\sum \limits_{d|n} \phi(d) = p_1^{a_1} p_2^{a_2} …p_k^{a_k} }\)
\({\ \ \ \ \ \ \ \ \ \ \ \ \ }\) \({=n}\)
得证。
- \({ \phi (a*b) = \phi(a) * \phi(b) * \frac{d}{\phi(d)}}\)
\({( \ d=gcd(a,b) \ )}\)
证明:
直接代入通式就好了。
现在我们来实际应用一下以上知识。
例题:P1390 公约数的和
题目描述:
给定 \({n}\) ,求 \({\sum \limits^{n}_{i=1} \sum \limits^{n}_{j=i+1} gcd(i,j).}\)
输入格式
输入只有一行一个整数,表示 \({n}\) 。
输出格式
输出一行一个整数表示答案。
说明/提示
\({2 \le n \le 2×10^6}\)
Code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2000000+5;
int n;
long long phi[maxn];
bool isPri[maxn];
vector<int>pri;
long long ans=0;
void ini() {
// phi[1]=1;
memset(isPri,true,sizeof(isPri));
isPri[1]=false;
for(int i=2;i<=n;i++) {
if(isPri[i]) {
phi[i]=i-1;
pri.push_back(i);
}
for(int j=0;j<pri.size();j++) {
int x=pri[j]*i;
if(x>n) break;
isPri[x]=false;
if(i%pri[j]!=0) phi[x]=phi[i]*phi[pri[j]];
else {
phi[x]=phi[i]*pri[j];
break;
}
}
}
return ;
}
int main() {
cin>>n;
ini();
for(int i=1;i*i<=n;i++) {
ans+=phi[i]*i;
for(int j=i+1;i*j<=n;j++)
ans+=phi[i]*j+phi[j]*i;
}
cout<<ans;
return 0;
}
【例题】P2568 GCD
题目描述
给定正整数 \(n\),求 \(1\le x,y\le n\) 且 \(\gcd(x,y)\) 为素数的数对 \((x,y)\) 有多少对。
输入格式
只有一行一个整数,代表\(n\)。
输出格式
一行一个整数表示答案。
样例 #1
样例输入 #1
4
样例输出 #1
4
提示
样例输入输出 1 解释
对于样例,满足条件的 \((x,y)\) 为 \((2,2)\),\((2,4)\),\((3,3)\),\((4,2)\)。
数据规模与约定
- 对于 \(100\%\) 的数据,保证 \(1\le n\le10^7\)。
参考程序:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+5;
int n,prime[700005],cnt=0,phi[maxn];
long long sum[maxn];
bool isPri[maxn];
long long ans=0;
void init() {
memset(isPri,true,sizeof(isPri));
isPri[1]=false;
phi[1]=1;
for(long long i=2;i<=n;i++) {
if(isPri[i]) {
prime[++cnt]=i;
phi[i]=i-1;
}
for(long long j=1;j<=cnt&&prime[j]*i<=n;j++) {
isPri[prime[j]*i]=false;
if(i%prime[j]==0) {
phi[prime[j]*i]=prime[j]*phi[i];
break;
}
else phi[prime[j]*i]=(prime[j]-1)*phi[i];
}
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+phi[i];
return ;
}
int main() {
cin>>n;
init();
for(int i=1;i<=cnt;i++)
ans+=2*sum[n/prime[i]]-1;
cout<<ans;
return 0;
}
第6题 GCD(X,N)>=M
给定整数N和M,问题是求有多少个整数X,满足1 <= X <= N且gcd(X,N)> = M。
输入格式
有T组测试数据。1 <= T <= 100
每组测试数据格式如下:
一行,两个整数N和M。 2<=N<=10^9, 1 <= M <= N.
输出格式
共T行,每行一个整数。
输入/输出例子1
输入:
3
1 1
10 2
10000 72
输出:
1
6
260
参考程序:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e9+5;
int G,n,m;
long long ans;
long long getPhi(int x) {
int re=x;
for(int i=2;i*i<=x;i++)
if(x%i==0) {
re=re*(i-1)/i;
while(x%i==0) x/=i;
}
if(x>1) re=re*(x-1)/x;
return re;
}
signed main() {
cin>>G;
while(G--) {
cin>>n>>m;ans=0;
for(int i=1;i*i<=n&&i<=n/m;i++) {
if(n%i==0) {
ans+=getPhi(i);
if(n/i<=n/m&&n/i!=i) ans+=getPhi(n/i);
}
}
cout<<ans<<endl;
}
return 0;
}
【例题】第7题 非互质数求和
如果一个正整数x小于N,而且x与N不互质,那么整数x称为“好数”。
给定一个正整数N,求所有的“好数”的和。
如果A,B除1外没有其他的公因数,则称A对B互质。
答案模1000000007。
输入格式
若干行,每行一个整数N(1≤N≤10^9)。
最后一行,N=0表示结束。
输出格式
若干行,每行一个整数。
输入/输出例子1
输入:
3
4
0
输出:
0
2
参考程序:
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,ans;
long long getPhi(long long x) {
long long re=x;
for(long long i=2;i*i<=x;i++)
if(x%i==0) {
re=re*(i-1)/i;
while(x%i==0)
x=x/i;
}
if(x>1) re=re*(x-1)/x;
return re;
}
int main() {
while(1) {
scanf("%lld",&n);
if(n==0) return 0;
ans=n*(n-1)/2;
printf("%lld\n",(ans-n*getPhi(n)/2)%mod);
}
return 0;
}
题解:
需要证明一个定理:
- 当 \({i}\) 与 \({n}\) 互质时,\({n-i}\) 也与 \({n}\) 互质
证明:
对于一个 \({i}\) 与 \({n}\) 互质,
则有 \({gcd(i,n)=1}\)
设 \({gcd(n-i,n)=k}\).
则有 \({ (n-i) \ mod \ k=0 , n \ mod \ k =0}\) .
则 \({i \ mod \ k =0}\).
则 \({gcd(i,n)=k}\).
又因为 \({gcd(i,n)=1}\)
所以 \({k=1}\).
得证。
【压轴题】USB的数学题
题目:求 \({F(n)= \sum \limits_{i=1}^{n} \frac{n}{gcd(i,n)}}\) .
输入格式
第一行,一个整数T。表示有T组测试数据。( T <= 1000000 )
接下来有T行,每行一个整数n。( n <= 10000000 )
输出格式
共T行,每行一个整数。
输入/输出例子1
输入:
10
39
12
37
15
91
63
32
87
28
7
输出:
1099
77
1333
147
6751
2623
683
5691
473
43
参考程序:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const long long maxn=1e7;
int G,cnt=0,micnt[maxn+5],mi[maxn+5],zhi[maxn+5];
bool isPri[maxn+5];
long long n,ans,phi[maxn+5],pri[700000+5],sum[maxn+5];
void init() {
memset(isPri,true,sizeof(isPri));
phi[1]=1;
sum[1]=1;
for(int i=2;i<=maxn;i++) {
if(isPri[i]) {
pri[++cnt]=i;
phi[i]=i-1;
sum[i]=phi[i]*i+1;
micnt[i]=1;
mi[i]=i;
zhi[i]=i;
}
for(int j=1;j<=cnt&&pri[j]*i<=maxn;j++) {
int ne=pri[j]*i;
isPri[ne]=false;
if(i%pri[j]==0) {
phi[ne]=pri[j]*phi[i];
mi[ne]=mi[i];
micnt[ne]=micnt[i]+1;
zhi[ne]=zhi[i]*mi[i];
if(zhi[ne]==ne) {
sum[ne]=1;
for(int k=1,l=mi[i];k<=micnt[i]+1;k++,l=l*mi[i])
sum[ne]+=phi[l]*l;
}
else sum[ne]=sum[zhi[i]*mi[i]]*sum[i/zhi[i]];
break;
}
else {
phi[ne]=phi[pri[j]]*phi[i];
sum[ne]=sum[pri[j]]*sum[i];
if(pri[j]<mi[i]) mi[ne]=pri[j],micnt[ne]=1,zhi[ne]=pri[j];
else mi[ne]=mi[i],micnt[ne]=micnt[i],zhi[ne]=zhi[i];
}
}
}
return ;
}
signed main() {
init();
scanf("%d",&G);
while(G--) {
scanf("%lld",&n);
printf("%lld\n",sum[n]);
}
return 0;
}
题解
容易发现 \({F(n)= \sum \limits_{d|n} d \phi (d)}\) .
然后我们还容易发现 \({F(n)}\) 是个积性函数.
用线性筛推即可。
注意当 \({i}\) 是 \({pri[j]}\) 为底数的幂时,我们不能靠拆分的方法造互质,这里用第一行的公式直接算出.