BZOJ2818-莫比乌斯反演/欧拉函数

这道题之前没有看数论函数的时候搞懂了,想到直接用欧拉函数做,现在再来看第一个想法就是这不是莫比乌斯反演嘛.

但还是能用简单数论知识直接做出来的还是尽量做简单一点.

两种方法想到后都写的差不多对了,都爆long long 了.万恶的long long .实在是烦.切记切记,只要是乘积,或者是累加的地方都要注意!!!

用欧拉函数做的话思路是:题目要求的是数对gcd(x,y)为素数的的个数,那么对于每个素数,我们要求的是符合范围内的gcd=1的个数(这个思路很常见,在数论函数中也是很常见的,因为我们不好直接处理gcd=k的情况,但是如果将情况转换为互质的情况我们就有很多的工具来进行处理),此时的范围变为1-n/k,即我们要求这个范围内互质的数的个数,我们不妨先令x<=y,则情况就是对欧拉函数求和,一般的话答案就是求和的两倍-1,之所以减1是因为唯一的一种x=y的情况x=y=1被算了两次(这种特殊情况需要注意).所以我们预处理出欧拉函数的前缀和就能很快解决问题.

需要理解的是这道题可以这样很简单的做是有局限性的,仅仅是因为x,y的范围是一样的因此可以这样分析,如果x,y的范围不一样就不可以.这也是一个小技巧吧,如果数对两个数的范围是不一样的一般没有办法简化只能踏踏实实的用数论函数解决.

还需要注意的一个技巧是如果只有一组数据,那么预处理的数据范围最好直接是读入的数据而不是最大的数据,这样可以减少很多时间.

AC代码

#include
#include
#include
#include
#include
#include
#include

using namespace std;

typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e7+5;

int phi[MAXN],prime[MAXN];
ll sum[MAXN];
bool check[MAXN]; int tot;

void pre(int maxn)
{
	tot=0; phi[1]=1;
	for(int i=2;i<maxn;i++)
	{
		if(!check[i])
		{
			prime[tot++]=i; phi[i]=i-1;
		}
		for(int j=0;j<tot && (ll)i*prime[j]<(ll)maxn;j++)
		{
			int x=prime[j]*i; check[x]=true;
			if(i%prime[j]) phi[x]=(prime[j]-1)*phi[i];
			else
			{
				phi[x]=prime[j]*phi[i];
				break;
			}
		}
	}
	for(int i=1;i<=maxn;i++) sum[i]=sum[i-1]+phi[i];
}

int main()
{
	int n;
	scanf("%d",&n);
	pre(n+3);
	
	ll ans=0;
	for(int j=0;j<tot && prime[j]<=n;j++)
	{
		ans+=2*(ll)sum[n/prime[j]]-1;
	}
	
	printf("%lld\n",ans);
	return 0;
}

再看这个的话我们还是对和式进行变形,对于每个质数p我们要求的是gcd=p的个数,记为f§,我们记g(x)为x|gcd 的个数,则由莫比乌斯反演得到f(x)和g(x)的关系,再用除法分块也能很快解决问题.同样的要注意某些地方会爆longlong

#include
#include
#include
#include
#include
#include
#include

using namespace std;

typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e7+5;

int prime[MAXN],mobius[MAXN],sum[MAXN];
bool check[MAXN]; int tot;

void pre(int maxn)
{
	ll x;
	tot=0; mobius[1]=sum[1]=1;
	for(int i=2;i<maxn;i++)
	{
		if(!check[i])
		{
			prime[tot++]=i; mobius[i]=-1;
		}
		for(int j=0;j<tot && (ll)prime[j]*i<(ll)maxn;j++)
		{
			x=prime[j]*i; check[x]=true;
			if(i%prime[j]) mobius[x]=-mobius[i];
			else 
			{
				mobius[x]=0; break;
			}
		}
		sum[i]=sum[i-1]+mobius[i];
	}
}

int n;

int main()
{
	
	while(~scanf("%d",&n))
	{
		pre(n+5);
		ll ans=0;
		for(int j=0;j<tot && prime[j]<=n;j++)
		{
			int t=n/prime[j];
			for(int l=1,r;l<=t;l=r+1)
			{
				r=t/(t/l);
				ans+=(ll)(sum[r]-sum[l-1])*(t/l)*(t/l);//没想到这里会爆longlong想了好久...
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

你可能感兴趣的:(#,数论)