题意:输入正整数n , 求gcd(1 ,2)+gcd(1 ,3)+gcd(2,3)+. . . +gcd(n-1,n)时,即所有满足1 <= i <j<=n 的数对(i,j)所对应的gcd(i,j)之和。比如n= l0 时答案为67 , n=100 时答案为13 015 , n=200 000时答案为143295493 160 。
设f(n)=gcd(1 ,n)+gcd(2 ,n)+gcd(3 ,n)+…+gcd(n-1, ,n),
所求答案为S(n)=f(2)+f(3)+…f(n) 。
只需求出f(n) , 就可以递推出所有答案: S(n)=S(n-1)+f(n) 。
注意到所有gcd(x,n)的值都是n 的约数,可以按照这个约数进行分类,用g(n, i)表示满足gcd(x,n)=i 且x<n 的正整数x 的个数,则f(n)=sum{i*g(n, i) | i 是n 的约数}。
注意到gcd(x,n)=i的充要条件是gcd(x/i,n/i)=1(此时x/i和n/i互质), 因此满足条件的x/i 有phi(n/i)个,说明g(n , i)=phi(n/i) 。
问题到这里还没有结束。如果依次计算j例,需要对每个n 枚举它的约数i , 速度较慢,但如果把思路逆转过来,对于每个i 枚举它的倍数n (并且更新j(n) 的值) ,时间复杂度将降为与素数筛法同阶。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define ll long long
#define maxn 4000010
bool flag[maxn];
int phi[maxn],prime[maxn];
ll s[maxn],f[maxn];
int num=0;
void euler()
{
phi[1]=1;
for(int i=2;i<=maxn;i++)
{
if(!flag[i])
{
prime[++num]=i;
phi[i]=i-1;
}
for(int j=1;j<=num&&(ll)prime[j]*i<=maxn;j++)
{
flag[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
}
int main()
{
euler();
for(int i=1;i<maxn;i++)
{
for(int n=i*2;n<maxn;n+=i)
{
f[n]+=i*phi[n/i];
}
}
s[2]=f[2];
for(int n=3;n<maxn;n++)
{
s[n]=s[n-1]+f[n];
}
int n;
while(scanf("%d",&n)!=EOF&&n)
{
printf("%lld\n",s[n]);
}
}
来源:https://blog.csdn.net/weixin_45113721/article/details/100980349