埃筛法求欧拉函数
普通的欧拉函数求解方法为 \(O(\log_{}{N} )\),可是当遇到下面这题时,阁下又该如何应对呢?
给定一个正整数 \(n\),求 \(1∼n\) 中每个数的欧拉函数之和。
其中,\(1 \leq n \leq 10^6\)。
很显然,传统方法复杂度为 \(O(n\log_{}{n})\),最大可达 \(10^9\),明显超时。
那么这里,我们提供一种可以在 \(\approx O(n\log_{}{\log{}{n}})\) 的时间复杂度解决的方法,埃筛法求欧拉函数。
首先,既然是埃筛,我们先把埃筛的代码写出来:
int func_Euler(int n)
{
for(int i=2;i<=n;i++){
if(!flag[i]) {
prime.push_back(i);
for(int j=i+i;j<=n;j+=i) flag[j]=1;
}
}
}
注:下记 \(\varphi (k)\) 为 \(k\) 的欧拉函数的值。
然后我们需要知晓一下几个结论:
-
若 \(k\) 为素数,则 \(\varphi (k)=k-1\)。
-
若有 \(k \bmod a = 0\)(\(a\) 为质数),则 \(\varphi (k \times a)=\varphi (k) \times a\)。
证明如下:
设 \(k\) 的质因子为 \(p_1 ... p_m\),由于 \(k \bmod a == 0\),即 \(a\) 已经在这些质因子中了,所以 \(k \times a\) 的质因子应该也为 \(p_1 ... p_m\)。
所以:
\(\varphi (k)=k \times (1-\frac{1}{p_1}) \times ... \times (1-\frac{1}{p_m})\)。
\(\varphi (k \times a)=k \times a \times (1-\frac{1}{p_1}) \times ... \times (1-\frac{1}{p_m})\)。
即:\(\varphi (k \times a)=\varphi (k) \times a\)。 -
否则,即 \(k \bmod a \ne 0\)(\(a\) 为质数),则 \(\varphi (k \times a)=\varphi (k) \times (a-1)\)。
证明如下:
设 \(k\) 的质因子为 \(p_1 ... p_m\),由于 \(k \bmod a \ne 0\),所以 \(a\) 必为新的质因子,所以 \(k \times a\) 的质因子为 \(p_1 ... p_m\) 和 \(a\)。
所以:
\(\varphi (k)=k \times (1-\frac{1}{p_1}) \times ... \times (1-\frac{1}{p_m})\)。
\(\varphi (k \times a)=k \times a \times (1-\frac{1}{p_1}) \times ... \times (1-\frac{1}{p_m}) \times (1-\frac{1}{a})\)。
这里 \(a\) 与相乘 \((1-\frac{1}{a})\) 抵消为 \(a-1\)。
即:\(\varphi (k \times a)=\varphi (k) \times (a-1)\)。
将上述结论结合到代码中去,即可(详见代码注释)。
时间复杂度为 \(O(n\log\log n)\)。
#include <bits/stdc++.h>
#define int long long //开long long,防止溢出
using namespace std;
const int N=1e6+5;
int phi[N]; //存储 φ(i) 的值
bool flag[N]; //是否为质数
vector<int> prime; //存储质数
int func_Euler(int n)
{
phi[1]=1; //φ(1)=1
for(int i=2;i<=n;i++){
if(!flag[i]) { //埃筛
phi[i]=i-1; //结论1
prime.push_back(i);
for(int j=i+i;j<=n;j+=i) flag[j]=1;
}
for(auto it:prime){ //枚举每个质数,即位上面讲的 a
if(i*it>n) break; //防止越界
if(i%it==0){ //结论2
phi[i*it]=phi[i]*it;
break;
}
else phi[it*i]=phi[i]*(it-1); //结论3
}
}
int ans=0;
for(int i=1;i<=n;i++) ans+=phi[i]; //求和
return ans;
}
signed main()
{
int n;cin>>n;
cout<<func_Euler(n)<<endl;
return 0;
}
upd2024.7.30:上述方法并不是最优的做法,注意到我们其实可以线性进行质数筛法,这样就可以做到 \(O(N)\) 的时间复杂度。(也不知道当时我为啥一半埃筛一半欧拉筛)。
#include <bits/stdc++.h>
#define int long long //开long long,防止溢出
using namespace std;
const int N=1e6+5;
int phi[N]; //存储 φ(i) 的值
bool flag[N]; //是否为质数
vector<int> prime; //存储质数
int func_Euler(int n)
{
phi[1]=1; //φ(1)=1
for(int i=2;i<=n;i++){
if(!flag[i]) { //埃筛
phi[i]=i-1; //结论1
prime.push_back(i);
}
for(auto it:prime){ //枚举每个质数,即位上面讲的 a
if(i*it>n) break; //防止越界
flag[i*it]=1;
if(i%it==0){ //结论2
phi[i*it]=phi[i]*it;
break;
}
else phi[it*i]=phi[i]*(it-1); //结论3
}
}
int ans=0;
for(int i=1;i<=n;i++) ans+=phi[i]; //求和
return ans;
}
signed main()
{
int n;cin>>n;
cout<<func_Euler(n)<<endl;
return 0;
}

浙公网安备 33010602011771号