题目大意
对于一个n个节点的图,节点编号为1到n,对于两个节点u,v,如果它们的gcd大于1,那么u与v之间有一条长度为1的边。
给定n,求节点两两之间距离之和(不连通则距离视为0)
n≤ 107
分析
首先容易发现,两个端点如果有一个编号为1或为大于
n2
的质数的点,那么这两点间一定没有路径。
接着两两间有路径的点对分以下几种情况:
1. 编号相同,距离为0
2. gcd大于1,距离为1
3. gcd等于1,设p[i]表示i的约数中的最小质数,当p[u]*p[v]≤n,时,可以构造出一条路径
u—>2∗p[v]—>v
,长度为2
4. gcd等于1,p[u]*p[v]>n,可以构造路径
p—>2∗p[u]—>2∗p[v]—>v
,长度为3
接下来就是统计每种情况的点对数了。
第二种情况可以用欧拉函数算。
对于第三、四中情况,可以算出它们的总和,然后统计第三种情况有多少个。
总和可以用欧拉函数算。
第三中情况又分两种:
1. u,v都是合数,那么枚举u,可能的v个数为
ϕ(u)−小于等于u的质数个数+u的质因子个数−1
2. u,v有至少一个质数,那么枚举u并令它为质数,可能的v个数为满足
p[u]∗p[v]≤n
的v个数减满足的v中u的倍数。这样子u,v都是质数的情况还会被算两次,要减重。
时间复杂度 O(n)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e7+5,M=4005;
typedef long long LL;
int n,tot,p[N],phi[N],sum[N],f[N],pc[N],cnt[N];
LL ans,now;
bool bz[N];
int main()
{
scanf("%d",&n);
for (int i=2;i<=n;i++)
{
if (!bz[i]) p[tot++]=i,phi[i]=i-1,f[i]=i,pc[i]=1;
cnt[i]=cnt[i-1]+(1^bz[i]);
sum[f[i]]++;
for (int j=0;j<tot && i*p[j]<=n;j++)
{
bz[i*p[j]]=1; f[i*p[j]]=p[j];
if (i%p[j]==0)
{
phi[i*p[j]]=phi[i]*p[j]; pc[i*p[j]]=pc[i]; break;
}
phi[i*p[j]]=phi[i]*(p[j]-1); pc[i*p[j]]=pc[i]+1;
}
}
for (int i=2;i<=n;i++) sum[i]+=sum[i-1];
now=1ll*sum[n/2]*(sum[n/2]-1)/2;
for (int i=2;i<=n;i++) if (bz[i])
{
ans+=i-phi[i]-1; now-=i-phi[i]-1;
ans+=2*(phi[i]-cnt[i]+pc[i]-1); now-=phi[i]-cnt[i]+pc[i]-1;
}
for (int i=2;i<=n/2;i++) if (!bz[i])
{
ans+=2*(sum[n/i]-n/i); now-=sum[n/i]-n/i;
ans-=2*cnt[min(i-1,n/i)]; now+=cnt[min(i-1,n/i)];
if (i>n/i) ans+=2*(sum[i]-sum[i-1]),now-=sum[i]-sum[i-1];
}
ans+=3*now;
printf("%I64d\n",ans);
return 0;
}