关于gcd的四道题

T1:

bzoj2705

题目描述:

给定一个n求(sumlimits_{i=1}^ngcd(i,n))
因为n太大,所以O(n)的做法肯定不行,然后就去想根号的方法。

[sumlimits_{i=1}^{n}gcd(i,n) ]

[=sumlimits_{k|n}k*sumlimits_{i=1}^n[gcd(i,n)==k] ]

[=sumlimits_{k|n}k*sumlimits_{i=1}^n[gcd(frac{i}{k},frac{n}{k})==1] ]

[=sumlimits_{k|n}k*sum_{i=1}^{frac{n}{k}}[gcd(i,frac{n}{k})==1] ]

[=sum_{k|n}{k*φ(frac{n}{k})} ]

然后i从1到(sqrt{n})去枚举n的因数,然后将i*φ(n/i)与n/i与φ(i)全部计入答案,就可以做到(sqrt{n}*sqrt{n})的复杂度,因为第二个根号是求欧拉函数的复杂度,所以实际的复杂度没有这么高

代码:

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
ll phi(ll x)
{
	ll ans=1;
	for(ll i=2;i*i<=x;++i)
	{
		if(x%i==0)
		{
			ans*=(i-1);
			x/=i;
		}
		while(x%i==0)
		{
			ans*=i;
			x/=i;
		}
	}
	if(x!=1)
	ans*=(x-1);
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	ll n;
	cin>>n;
	ll ans=0;
	ll i;
	for(i=1;i*i<=n;++i)
		if(n%i==0)
			ans+=i*phi(n/i)+(n/i)*phi(i);
	if(i*i==n) ans-=i*phi(i);
	cout<<ans;
	return 0;
}

T2:

exbzoj2705:

没有评测,

题目描述:

给定一个整数n(1<=n<=100000),你需要求出(sumlimits_{i=1}^nsumlimits_{j=1}^igcd(i,j))
暴力做法:将上个题中的n循环起来,最后记录每个循环所求的和。明显TLE
正解:

[sumlimits_{i=1}^nsumlimits_{j=1}^igcd(i,j) ]

枚举因数k

[sumlimits_{k=1}^nk*sumlimits_{i=1}^nsumlimits_{j=1}^i[gcd(i,j)==k] ]

考虑所有最大公因数为k的情况,设(i=ak,j=bk(a>=b))若要i与j做大公约数为k,则必须满足gcd(a,b)=1,满足此条件的所有情况数为φ(b),然后考虑b的取值范围,因为必须满足b*k<=n,所以(b<=[frac{n}{k}])。所以答案为

[sumlimits_{k=1}^nk*sumlimits_{i=1}^{frac{n}{k}}φ(i) ]

所以线性求出欧拉函数,并求出前缀和即可。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=100000+100;
int phi[N],phi_sum[N];
void getphi()
{
	for(int i=1;i<N;++i)
		phi[i]=i;
	phi[1]=1;
	for(int i=2;i<N;++i)
		if(phi[i]==i)
			for(int j=i;j<=N;j+=i)
				phi[j]=phi[j]/i*(i-1);
	for(int i=1;i<N;++i)
		phi_sum[i]=phi_sum[i-1]+phi[i];
}
int n;
int main()
{
	getphi();
	while(1)
	{
		scanf("%d",&n);
		if(!n) break;
		long long ans=0;
		for(int i=1;i<=n;++i)
			ans+=phi_sum[n/i]*i;
		cout<<ans<<endl;
	}
	return 0;
}

T3:

uva11417

别问我为什么是luogu

题目描述:

给定一个整数n(1<=n<=100000),求(sumlimits_{i=1}^{n-1}sumlimits_{j=i+1}^ngcd(i,j))

解法:

[sumlimits_{i=1}^{n-1}sumlimits_{j=i+1}^ngcd(i,j) ]

枚举因数k

[sumlimits_{k=1}^nk*sumlimits_{i=1}^{n-1}sumlimits_{j=i+1}^n[gcd(i,j)==k] ]

考虑所有最大公因数为k的情况,设(i=ak,j=bk(b>a))若要i与j做大公约数为k,则必须满足gcd(a,b)=1,满足此条件的所有情况数为φ(b),然后考虑b的取值范围,因为必须满足b*k<=n,所以(b<=[frac{n}{k}])。所以答案为

[sumlimits_{k=1}^nk*sumlimits_{i=1}^{frac{n}{k}}φ(i) ]

所以线性求出欧拉函数,并求出前缀和即可。
为什么和上面一样
但是因为i和j都不能为0并且j>i即b>a,所以b不能为1,所以要在最后减去φ(1)的情况,也就相当于把里面的i从2开始枚举。
所以最终答案为

[sumlimits_{k=1}^nk*sumlimits_{i=1}^{frac{n}{k}}φ(i)-1 ]

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int N=100000+100;
int phi[N],phi_sum[N];
void getphi()
{
	for(int i=1;i<N;++i)
		phi[i]=i;
	phi[1]=1;
	for(int i=2;i<N;++i)
		if(phi[i]==i)
			for(int j=i;j<=N;j+=i)
				phi[j]=phi[j]/i*(i-1);
	for(int i=1;i<N;++i)
		phi_sum[i]=phi_sum[i-1]+phi[i];
}
int n;
int main()
{
	getphi();
	while(1)
	{
		scanf("%d",&n);
		if(!n) break;
		long long ans=0;
		for(int i=1;i<=n;++i)
			ans+=(phi_sum[n/i]-1)*i;
		cout<<ans<<endl;
	}
	return 0;
}

T4:

luogu2398

题目描述:

给定一个n(1<=n<=100000),求(sumlimits_{i=1}^nsumlimits_{j=1}^ngcd(i,j))

解法:

发现这个题数上面两个题的综合,所以,嘿嘿,将上面的两个题答案加起来即可,所以最终答案为

[sumlimits_{k=1}^n2*k*sumlimits_{i=1}^{frac{n}{k}}φ(i)-1 ]

代码:

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N=100000+100;
ll ans,phi[N],phi_sum[N],n;
void getphi()
{
	for(int i=1;i<N;++i)
		phi[i]=i;
	phi[1]=1;
	for(int i=2;i<N;++i)
		if(phi[i]==i)
			for(int j=i;j<=N;j+=i)
				phi[j]=phi[j]/i*(i-1);
	for(int i=1;i<N;++i)
		phi_sum[i]=phi_sum[i-1]+phi[i];
}
int main()
{
	cin>>n;
	getphi();
	for(int i=1;i<=n;++i)
		ans+=(phi_sum[n/i]*2-1)*i;
	cout<<ans;
	return 0;
}
原文地址:https://www.cnblogs.com/wxyww/p/9534392.html