window.cnblogsConfig = {//可以放多张照片,应该是在每一个博文上面的图片,如果是多张的话,那么就随机换的。 homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

埃筛法求欧拉函数

普通的欧拉函数求解方法为 \(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\) 的欧拉函数的值。

然后我们需要知晓一下几个结论:

  1. \(k\) 为素数,则 \(\varphi (k)=k-1\)

  2. 若有 \(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\)

  3. 否则,即 \(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; 
} 
posted @ 2023-12-03 19:03  ziyistudy  阅读(37)  评论(0)    收藏  举报