题意
有一个n个节点的图,节点i和节点j之间有长度为1的连边当且仅当
gcd(i,j)≠1
g
c
d
(
i
,
j
)
≠
1
。
设
dis(u,v)
d
i
s
(
u
,
v
)
表示u到v的最短路,若u不能到达v则
dis(u,v)=0。
d
i
s
(
u
,
v
)
=
0
。
求
∑u<vdis(u,v)
∑
u
<
v
d
i
s
(
u
,
v
)
。
n<=107
n
<=
10
7
分析
不难发现若路径两端的其中一个端点是
1
1
或是大于的素数则必然为0,那么我们就不考虑这两种数。
设
p[u]
p
[
u
]
表示u的最小素因子,现在剩下的路径分成若干种情况:
gcd(u,v)>1
g
c
d
(
u
,
v
)
>
1
则
dis(u,v)=1
d
i
s
(
u
,
v
)
=
1
gcd(u,v)=1,p[u]∗p[v]<=n
g
c
d
(
u
,
v
)
=
1
,
p
[
u
]
∗
p
[
v
]
<=
n
则
dis(u,v)=2
d
i
s
(
u
,
v
)
=
2
gcd(u,v)=1,p[u]∗p[v]>n
g
c
d
(
u
,
v
)
=
1
,
p
[
u
]
∗
p
[
v
]
>
n
则
dis(u,v)=3
d
i
s
(
u
,
v
)
=
3
第一种情况显然可以用欧拉函数来算。
考虑先求出后两种情况的数量和第三种情况的数量。
对于后两种情况的数量,也可以用欧拉函数来算。
对于第三种情况,显然
u
u
和必有一个是大于
n−−√
n
的素数,那么就枚举这个素数,设为
p1
p
1
然后找到最小的素数
p2
p
2
满足
p1∗p2>n
p
1
∗
p
2
>
n
,然后对于每个素数预处理有多少个数的最小素因子为它,再用前缀和优化一下即可。
时间复杂度
O(n)
O
(
n
)
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int N=10000005;
int n,tot,prime[N],phi[N],low[N],s[N];
bool not_prime[N];
void get_prime(int n)
{
phi[1]=1;
for (int i=2;i<=n;i++)
{
if (!not_prime[i]) prime[++tot]=i,phi[i]=i-1,low[i]=tot;
for (int j=1;j<=tot&&i*prime[j]<=n;j++)
{
not_prime[i*prime[j]]=1;
low[i*prime[j]]=j;
if (i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for (int i=2;i<=n;i++) s[low[i]]++;
while (prime[tot]>n/2) tot--;
for (int i=tot-1;i>=1;i--) s[i]+=s[i+1];
}
int main()
{
scanf("%d",&n);
get_prime(n);
LL ans=0,sum=0;int now=0;
for (int i=2;i<=n;i++)
{
ans+=i-1-phi[i];
if (i<=n/2||not_prime[i]) sum+=phi[i]-1-now;
else now++;
}
ans+=sum*2;sum=0;
int p=tot,lim=sqrt(n),cnt=0;
for (int i=1;i<=tot;i++)
{
if (prime[i]<=lim) continue;
while ((LL)prime[p-1]*prime[i]>(LL)n) p--;
sum+=s[p];cnt++;
}
ans+=sum-(LL)cnt*(cnt+1)/2;
printf("%I64d",ans);
return 0;
}