欧拉函数
Part 1.什么是欧拉函数?
在数论,对正整数 \(n\) ,欧拉函数是小于或等于 \(n\) 的正整数中与 \(n\) 互质的数的数目(因此 \(\varphi(1)=1\))。此函数以其首名研究者欧拉命名 (Euler) ,它又称为 Euler's totient function、\(\varphi\) 函数、欧拉商数等。——百度百科
Part 2.一些性质
-
若 \(n\) 为质数,则 \(\varphi(n)=n-1\)。
-
\(\forall n>1\),\([1,n]\) 中与 \(n\) 互质的数的和为 \(\dfrac{n\times\varphi(n)}{2}\)。
-
若 \(a,b\) 互质,则 \(\varphi(a\times b)=\varphi(a)\times\varphi(b)\)。
-
若 \(a,m\) 互质,则 \(a^{\varphi(m)}\equiv 1\pmod m\)。
-
\(\sum_{d\mid n}\varphi(d)=n\)。
-
若 \(n\bmod p=0\),则 \(\varphi(n\times p)=\varphi(n)\times p\)。 若 \(n\bmod p\not= 0\),则 \(\varphi(n\times p)=\varphi(n)\times (p-1)\)。
Part 3.怎么求?
1.通式
假设正整数 \(n\) 有 \(m\) 个质因数,分别为 \(p_1,p_2,p_3,\cdots,p_m\),则 \(\varphi(n)\) 有以下通式:
即:
推导过程的话,懒得打了...放个链接吧。
根据通式我们可以写出以下代码:
int euler(int n)
{
int ans=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
2.筛法
百度百科上的筛法版本:
/*
特性 :
1.若a为质数,phi[a]=a-1;
2.若a为质数,b mod a=0,phi[a*b]=phi[b]*a
3.若a,b互质,phi[a*b]=phi[a]*phi[b](当a为质数时,if b mod a!=0 ,phi[a*b]=phi[a]*phi[b])
*/
int m[n],phi[n],p[n],nump;
//m[i]标记i是否为素数,0为素数,1不为素数;p是存放素数的数组;nump是当前素数个数;phi[i]为欧拉函数
int main()
{
phi[1]=1;
for (int i=2;i<=n;i++)
{
if (!m[i])//i为素数
{
p[++nump]=i;//将i加入素数数组p中
phi[i]=i-1;//因为i是素数,由特性得知
}
for (int j=1;j<=nump&&p[j]*i<=n;j++) //用当前已得到的素数数组p筛,筛去p[j]*i
{
m[p[j]*i]=1;//可以确定i*p[j]不是素数
if (i%p[j]==0) //看p[j]是否是i的约数,因为素数p[j],等于判断i和p[j]是否互质
{
phi[p[j]*i]=phi[i]*p[j]; //特性2
break;
}
else phi[p[j]*i]=phi[i]*(p[j]-1); //互质,特性3其,p[j]-1就是phi[p[j]]
}
}
}
Part 4.例题
(1)SPOJ4141 Euler Totient Function
模板题,不做分析。
#include<iostream>
#include<cstdio>
using namespace std;
int euler(int n)
{
int ans=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
cout<<euler(n)<<endl;
}
return 0;
}
(2)HDU2588 GCD
设 \(p=\gcd(x,n),\;n=p\times a,\;x=p\times b\),显然 \(a\) 和 \(b\) 一定互质。
所以我们可以枚举公约数 \(p\),通过 \(n\div p\) 求出 \(a\),然后求区间 \([1,a]\) 内有多少个与 \(a\) 互质的 \(b\) 即可。
我们可以用上面的欧拉函数 \(\varphi(a)\) 来求与 \(a\) 互质的数。
code:
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
ll euler(ll n)
{
ll ans=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
ll ans=0;
for(int i=1;i*i<=n;i++)
{
if(n%i==0) //i或者n/i为公约数的情况
{
if(i>=m) ans+=euler(n/i); //如果i为公约数
if(i*i!=n && n/i>=m) ans+=euler(i); //如果n/i为公约数
}
}
printf("%lld\n",ans);
}
return 0;
}
(3)BZOJ2818 Gcd
设 \(\gcd(x,y)=k\),则有 \(\gcd(x\div k,y\div k)=1\)。
此时我们只需求区间 \([1,n\div p_i]\) 内有多少个互质的 \((x,y)\) 即可。
\((x,y)\) 和 \((y,x)\) 视为两个不同的二元组,所以只需求出 \((x,y)\) 即可,之后再 \(\times 2\)。可是,如果按上述算法计算,\((1,1)\) 会重复算两遍,所以要减去 \(1\)。
code:
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int N=10000005;
int n,p,cnt,phi[N],pri[N];
bool book[N];
ll ans,sum[N];
void getphi() //筛法求φ(1~n)顺便求1~n的所有质数
{
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!book[i])
{
phi[i]=i-1;
pri[++cnt]=i;
}
for(int j=1;j<=cnt && i*pri[j]<=n;j++)
{
book[i*pri[j]]=1;
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
else phi[i*pri[j]]=phi[i]*phi[pri[j]];
}
}
}
int main()
{
scanf("%d",&n);
getphi();
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+phi[i];
for(int i=1;i<=cnt;i++) ans+=sum[n/pri[i]]*2-1;
printf("%lld",ans);
return 0;
}