数论技巧——使用线性筛法去求1~n之间欧拉函数的和

         本节是数论中的重要内容,也是算法竞赛中的常考点,初学者理解起来可能有些困难,需要多多体会

   给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

 欧拉函数的定义:1~n中与n互质的数的个数被称为欧拉函数,记作φ(n)

欧拉函数的性质:

1.若p是质数,则φ(p) = p-1

2.若p是质数,则φ(p^k) = (k-1)*p^(k-1)

3.欧拉函数是积性函数 ,若 gcd(m,n) = 1,则有φ(m,n) = φ(m)*φ(n)

第一个性质很好证明

第二个证明如下:

已知p^k一定可以整除 p,p^2,p^3......p^k-1

那么我们可以得出,在p^k-1与p^k之间,不可能存在别的数能整除p (p是质数)

在一个循环节中,我们可以知道共有(p-1)个数是与p互质的,那么个数是(p-1)

并且循环节一共有p^k-1个,所以定理二成立

定理三的证明读者可自行查阅资料(本觏婼不会)

欧拉函数的计算公式:

由唯一分解性定理可知,n = p1^a1*p2^a2......pk^ak

则φ(n) = \prodφ(pi^ai)

           = \prod(pi-1)*p^(ai-1)

           = \prodp^ai*\prod(1-(1/pi))

           = n*\prod(1-(1/pi))

所以φ(n) = n*((p1-1)/p1)*((p2-1)/p2).....

欧拉函数仅与n和其只因子有关,与次数无关

这时我们可以根据定义,用试除法来求欧拉函数

int phi(int n) // 用试除法求欧拉函数 
{
	int res = n;
	for(int i=2;i<=n/i;i++)
	{
		if(n%i==0)
		{
			res = res*(i-1)/i;
			while(n%i==0) n /= i;
		}
	}
	if(n>0) res = res*(n-1)/n;
	return res;
}

此算法的复杂度较高,有局限性,我们考虑优化

使用线性筛法求欧拉函数

在线性筛法中,每个合数m都是被其最小的只因子筛掉的

我们从小到大枚举i,并且满足不超过n

若i可以整除pj(最小只因子),那么i包含了m的所有只因子         φ(m) = pj*φ(i)

否则的话i与pj 是互质的        φ(m) = φ(pj)*φ(i) = (pj-1)*(φ(i))

const int N = 100010;
int p[N],cnt; // p数组是存储只因子个数,cnt记录数量 
int phi[N];  // phi 是欧拉函数 
bool vis[N]; // 标记应该去掉的合数 

void get_phi(int n) // 线性筛法求欧拉函数 
{
	phi[1] = 1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) // 没有划掉的话证明i是质数 
		{
			p[cnt++] = i; // 将这个最小只因子存储下来 
			phi[i] = i-1; // 定理一 
		}
		for(int j=0;i*p[j]<=n;j++)
		{
			// 这里的步骤已经给出证明 
			int m = i*p[j];
			vis[m] = false;
			if(i%p[j]==0)
			{
				phi[m] = phi[i]*p[j];
				break;
			}
			else phi[m] = phi[i]*(p[j]-1);
		}
	}
}

下面的话给出本题的完整代码:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 1000010;
typedef long long LL;
int p[N],cnt;
int phi[N];
bool vis[N];

void get_phi(int n)
{
	phi[1] = 1;
	
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		{
			p[cnt++] = i;
			phi[i] = i-1;
		}
		for(int j=0;p[j]*i<=n;j++)
		{
			int m = i*p[j];
			vis[m] = true;
			if(i%p[j]==0)
			{
				phi[m] = phi[i]*p[j];
				break;
			}
			else phi[m] = phi[i]*(p[j]-1);
		}
	}
	return;
}

int main(void)
{
	int n;cin >> n;
	LL res = 0;
	
	get_phi(n);
	for(int i=1;i<=n;i++)
		res+=phi[i]; 
	
	cout << res << endl;
	return 0;
 } 

如果你还是不太懂的话,建议去小破站看看董晓老师讲的算法课

飞机票:【G09 筛法求欧拉函数】https://www.bilibili.com/video/BV1VP411p7Bs?vd_source=703af51dc566d98822b7573adab52f24

感谢查看,如果对你有帮助的话,点个赞呗

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值