关于gcd的四道题
T1:
题目描述:
给定一个n求\(\sum\limits_{i=1}^ngcd(i,n)\)
因为n太大,所以O(n)的做法肯定不行,然后就去想根号的方法。
然后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),你需要求出\(\sum\limits_{i=1}^n\sum\limits_{j=1}^igcd(i,j)\)
暴力做法:将上个题中的n循环起来,最后记录每个循环所求的和。明显TLE
正解:
枚举因数k
考虑所有最大公因数为k的情况,设\(i=ak,j=bk(a>=b)\)若要i与j做大公约数为k,则必须满足gcd(a,b)=1,满足此条件的所有情况数为φ(b),然后考虑b的取值范围,因为必须满足b*k<=n,所以\(b<=[\frac{n}{k}]\)。所以答案为
所以线性求出欧拉函数,并求出前缀和即可。
代码:
#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:
别问我为什么是luogu
题目描述:
给定一个整数n(1<=n<=100000),求\(\sum\limits_{i=1}^{n-1}\sum\limits_{j=i+1}^ngcd(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}]\)。所以答案为
所以线性求出欧拉函数,并求出前缀和即可。
为什么和上面一样
但是因为i和j都不能为0并且j>i即b>a,所以b不能为1,所以要在最后减去φ(1)的情况,也就相当于把里面的i从2开始枚举。
所以最终答案为
代码:
#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:
题目描述:
给定一个n(1<=n<=100000),求\(\sum\limits_{i=1}^n\sum\limits_{j=1}^ngcd(i,j)\)
解法:
发现这个题数上面两个题的综合,所以,嘿嘿,将上面的两个题答案加起来即可,所以最终答案为
代码:
#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;
}