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 imesvarphi(n)}{2})。
-
若 (a,b) 互质,则 (varphi(a imes b)=varphi(a) imesvarphi(b))。
-
若 (a,m) 互质,则 (a^{varphi(m)}equiv 1pmod m)。
-
(sum_{dmid n}varphi(d)=n)。
-
若 (nmod p=0),则 (varphi(n imes p)=varphi(n) imes p)。 若 (nmod p ot= 0),则 (varphi(n imes p)=varphi(n) imes (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 imes a,;x=p imes b),显然 (a) 和 (b) 一定互质。
所以我们可以枚举公约数 (p),通过 (ndiv 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
",ans);
}
return 0;
}
(3)BZOJ2818 Gcd
设 (gcd(x,y)=k),则有 (gcd(xdiv k,ydiv k)=1)。
此时我们只需求区间 ([1,ndiv p_i]) 内有多少个互质的 ((x,y)) 即可。
((x,y)) 和 ((y,x)) 视为两个不同的二元组,所以只需求出 ((x,y)) 即可,之后再 ( imes 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;
}