题目:
给出给定正整数n,求,此处f(n)定义为小于等于n并且与n互质的数的个数(此处认为1与1互质)。
所谓的f(n)就是欧拉函数,只是对1做了一下重新定义,整体不影响。
题目求的是从1到n的所有欧拉函数的和
如何求欧拉函数,可以看这个前几天才写的
但是逐个求欧拉函数毫无疑问时间会爆的。
如果能知道欧拉函数的一些性质,特别是这个:
如果a和b互质,则f(a*b)=f(a)*f(b);
那想必马上就能想到要用动态规划求解
我们现在想一个for循环,i从2到n,如何构建向前的递推式?
因为i本身不一定是质数,那什么数肯定和i互质?当然是质数,那么遍历小于i的质数肯定能找到和i互质的数了。然后相乘递推。
由于质数是共用的,所以可以开一个全局数组来储存质数,如果i本身是质数,则把i加入到质数的数组中,这样质数数组也随着i的循环而逐渐增加
直觉觉得这样好像会漏数,但实际上不会。如果一个数是质数,它会被加到质数数组里,而一个合数肯定可以写成许多质数的乘积。
来看一下代码
其中primer是质数数组,judge[i]用来判断i是否是质数,phi[i]即是欧拉函数,cnt是primer数组的大小,sum是欧拉函数的求和值。
#include<iostream>
using namespace std;
#define int long long
const int N=1e6+7;
int primer[N],judge[N],phi[N],n,sum,cnt=0;
void getphi(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!judge[i]){//judge[i]==false代表当前i是质数
primer[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;primer[j]*i<=n;j++){
judge[primer[j]*i]=true;
if(i%primer[j]==0){
phi[i*primer[j]]=primer[j]*phi[i];
break;
}
else phi[i*primer[j]]=(primer[j]-1)*phi[i];
}
}
}
signed main()
{
cin>>n;
getphi(n);
for(int i=1;i<=n;i++)
sum+=phi[i];
cout<<sum;
return 0;
}
if(!judge[i])需要配合judge[primer[j]*i]=true来看
我们在向后递推的时候,i*primer[j]的值肯定是个合数,那么judge[i*primer[j]]赋值成true标记它为合数。
因为我们是向后递推,遍历到i的时候phi(i)如果算好了,那么judge[i]肯定是true了,那i肯定就不是质数。如果i是质数,那么phi(i)就没有赋值,因为i是质数,phi(i)=i-1;
if(i%primer[j]==0){
phi[i*primer[j]]=primer[j]*phi[i];
break;
}
else phi[i*primer[j]]=(primer[j]-1)*phi[i];
这段代码是最重要的,我们知道i肯定可以写成(p,q等都是质因数,n,m是指数)的形式,这个if else语句相当于找到 i 乘上 所有 小于等于 i的最小质因数 的质数 的积,然后求出这个乘积的欧拉函数
这样会不会漏数?我举个例子先说明一下
比如i=15,它是3*5,那么质数到3就结束了,最后求了30和45的欧拉函数,有人会问那5没遍历到的话75的欧拉函数是怎么来的?
实际上在i=25的时候,它在质数遍历到3的时候还没结束呢,这时候就会求出75的欧拉函数了。
这实际上是线性筛的思想,一个数只被它的最小质因数标记,每个数只有一个最小质因子,所以每个数都只会筛一遍。想要了解的话可以自行学习。
总而言之,这样是不会漏数的。
但问题是i如果是当前质数的倍数,欧拉函数该怎么算?
我们知道欧拉函数的计算是看质因数的,设m=i*primer[j],实际上m的质因数不用找了,就是i的质因数,因为primer[j]是i的因数,primer[j]的所有质因数当然也被包括在i的质因数里。公式 m后面的部分 和
里i后面的部分是完全一样的,而i*primer[j]=m,所以f(m)=primer[j]*f(i);
而如果i不是当前质数的倍数,那么i和primer[j]互质,那就是最简单的公式了
综上,整个代码就写完了