【复习】欧拉函数
首先让我们来复习以下欧拉函数的概念。
- 写作\(phi(i)\),表示小于\(i\)的与\(i\)互质的数的个数
- 特殊的,\(phi(1)=1\);
根据定义我们可以得到其推导方法。
- 对于任意的\(i∈[2,INF]\),\(i\)都可以被拆分为\(p1^{c1}*p2^{c2}*...pn^{cn}\)的形式,其中\(pi\)表示素数,而\(ci\)表示素数的次数,即把一个数拆成素数乘积的形式。
- 所以利用容斥原理,我们可得\(phi(i)\)的推导:\(phi(i)=N(1-1/p1)...(1-1/pn)\);
它有一些很优秀的性质:
- \(phi\)是积性函数。对于任意满足\(gcd(a,b)=1\)的\(a,b\),都满足下面这个式子:\(phi(ab)=phi(a)phi(b)\);
- 若\(p|n\)且\(p^2|n\),则有\(phi(n)=phi(n/p)*p\)
- 证明:若满足以上条件,则n和n/p的素数组成相同,不同的只有次数。根据定义式,可以得到\(phi(n)=phi(n/p)*p\).
- 若\(p|n\)且\(p^2\)不被\(n\)整除,则有\(phi(n)=phi(n/p)*(p-1)\)
- 证明:若满足\(p|n\)且\(p^2\)不被\(n\)整除,则\(n\)和\(n/p\)互质。满足\(phi(n)=phi(n/p)*phi(p)=phi(n)=phi(n/p)*(p-1)\)
那么关键来了:我们要利用这些性质求解欧拉函数。
利用定义式,我们可以很容易想到常规推导:
for(register int i=1;i<=n;++i)phi[i]=i;//先记为其本身
for(register int i=2;i<=n;++i){
if(phi[i]==i){//质数
for(register int j=i;j<=n;j+=i){//处理后面的每一个i的倍数
phi[j]=phi[j]/i*(i-1);//利用定义式计算非积性求解情况
}
}
}
}
这个是建立在埃氏筛基础上的欧拉函数求法,复杂度是O(nlogn),足以水过P2158 40000 的数据范围。但是如果数据更大的话,这种算法很显然是不优秀的,我们就要考虑更快的算法,于是便想到了同一个人名字命名的欧拉筛。(欧拉全家桶.jpg)
首先先考虑简单的欧拉筛求素数集。
vis[1]=1;//vis记录素数情况,0为素数
for(register int i=2;i<=n;++i){
if(!vis[i])prime[++tot]=i;//素数
for(register int j=1;j<=tot&&i*prime[j]<=n;++j){
vis[i*prime[j]]=1;//合数标为1
if(i%prime[j]==0)break;//j以后的都可以由更小的素数筛得
//如i*prime[j+1]中i本身可以被分解为比prime[j+1]更小的质数。
}
}
同理,很容易就可以想到怎么对欧拉函数求解了。
for(register int i=1;i<=n;++i)phi[i]=i;
for(register int i=2;i<=n;++i){
if(phi[i]==i){//i为质数
prime[++cnt]=i;//记录这个数位质数,最小质因子是它自己
phi[i]=i-1;
}
for(register int j=1;j<=cnt&&i*prime[j]<=n;++j){//不超出n的范围
if(i%prime[j]!=0){
phi[i*prime[j]]=phi[i]*(prime[j]-1);
//如果i%prime[j]!=0,则i*prime[j]和prime[j]互质
}else{
phi[i*prime[j]]=phi[i]*(prime[j]);
break;
}
}
}
就是这样~
什么时候会用到欧拉函数呢?通常我们要把它从复杂的模型里提取出来。例如[P2158 SDOI2008]仪仗队这个题目,就需要我们想到,对于任意一个首次出现的斜率,其gcd(x,y)一定为1即满足互质。认真思考后就会发现完全就是一个欧拉函数求和啦~
\(UPD\):关于欧拉筛的那个\(break\)作用的考虑。
毫不夸张的说,欧拉筛中的那个\(break\)是整个算法中最精华最让人赞叹的地方。下面我们来考虑一下这种情况:
- \(prime[ ]\)中存储了所有的素数
- 在欧拉筛中,一旦出现\(i\%prime[j]==0\),就在进行完本次运算后停止。
- 原因:既然已经有\(i\%prime[j]=0\),那么\(prime[j]\)就是\(i\)的本身组成。在继续往后找的过程中,\(prime[j]\)只会越来越大。为了符合只让\(i\)被其最小质因子筛掉一次的条件,我们在做完这次循环后就\(break\).