BZOJ 2818 Gcd - Euler函数/莫比乌斯反演

本文介绍了一种使用Euler函数和莫比乌斯反演解决数学问题的方法,具体为求解特定范围内两数最大公约数等于某定值的组合数量。通过线性筛法预处理Euler函数和莫比乌斯函数,并结合前缀和优化算法,实现高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

考察内容:Euler函数


题意:求Σ(1<=i,j<=n,gcd(i,j)=p)1


分析:
若gcd(i,j)=p,将i与j同除以p,则gcd(i/p,j/p)=1,而i,j最大值为n,则在p的情况下即为求i/p(i<=n)与多少小于它的数互质,值为phi[1/p]+…+phi[n/p]值,枚举每一个p,将Σphi利用一个前缀和预处理一下,最后减去质数个数即可((p,p)只算一次)。

注意开long long不然会炸


Euler函数

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=10000005;

int n,cnt,k;
bool is[maxn];
long long pre[maxn];
int prime[maxn];
int phi[maxn];

void linear_shaker()
{
	int t;
	phi[1]=1;
	for(int i=2;i<maxn;i++)
	{
		if(!is[i])
		{
			phi[i]=i-1;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt&&(t=prime[j]*i)<maxn;j++)
		{
			is[t]=true;
			if(i%prime[j]==0)
			{
				phi[t]=phi[i]*prime[j];
				break;
			}
			phi[t]=phi[i]*phi[prime[j]];
		}
	}	
}
int main()
{
	scanf("%d",&n);
	long long ans=0;
	linear_shaker();
	pre[1]=1;
	for(int i=2;i<=n;i++)
		pre[i]=pre[i-1]+phi[i];
	int i;
	for(i=1;i<=cnt;i++)
	{
		ans+=pre[n/prime[i]];
		if(prime[i]>n)break;
	}
	i--;
	printf("%lld",(ans<<1)-i);//(a,b)与(b,a)应计算两次 
	//要注意的是当p为质数时,(p,p)只能计算一次(而默认计算了两次),所以需要减去已扫过的质数个数 
	
}

还有一种做法是利用莫比乌斯反演

#include<iostream>
#include<cstring> 
#include<cstdlib>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=10000010;

int n,cnt;
int prime[maxn];
bool is[maxn];
int miu[maxn];
long long ans;

void linear_shaker()
{
	int tmp;
	miu[1]=1;
	for(int i=2;i<maxn;i++)
	{
		if(!is[i])
		{
			miu[i]=-1;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt&&(tmp=prime[j]*i)<maxn;j++)
		{
			is[tmp]=true;
			if(i%prime[j]==0)
			{
				miu[tmp]=0;
				break;
			}
			miu[tmp]=-miu[i];
		}
	}
	for(int i=2;i<maxn;i++)
		miu[i]+=miu[i-1];
}
long long getans(long long m,int k)
{
	long long res=0;
	int pos;
	m/=k;
	for(int i=1;i<=m;i=pos+1)
	{
		pos=(m/(m/i));
		res+=1LL*(miu[pos]-miu[i-1])*(m/i)*(m/i);
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	linear_shaker();
	for(int i=1;i<=cnt&&prime[i]<=n;i++)ans+=getans(n,prime[i]);
	printf("%lld",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值