文章目录
题目链接:
http://www.51nod.com/Challenge/Problem.html#!#problemId=1237
我们想要求所有的 gcd(i,j)的和,换一个思路就是看 gcd(i,j)=d对答案的贡献,(感觉好多题都是换一种思路来看对答案的贡献的),那如果能够快速求出 gcd(i,j)=d的有几个就好了
假如说 gcd(i,j)=d的有 cnt(d)个,那么 ans=d=1∑nd⋅cnt(d)
那么现在结论就是:
cnt(d)=2⋅S(dn)−1
其中S(n)表示欧拉函数前缀和:
S(n)=i=1∑nφ(i)
那么为什么喃?
先来看这个:
i=1∑nj=1∑n[gcd(i,j)==1]=2i=1∑nj=1∑i−1[gcd(i,j)==1]+1
这个还是很好理解的,因为 gcd(i,j)和 gcd(j,i)是对称的,主对角线分成两半就行
加1是因为 gcd(1,1)=1
然后再修改一哈,把 gcd(1,1)加进去, j从 1到 i而不是 i−1了
i=1∑nj=1∑n[gcd(i,j)==1]=2i=1∑nj=1∑i[gcd(i,j)==1]−1
有了这个之后,来看里面那层求和,其实这层求和就是欧拉函数的定义,所以
j=1∑i[gcd(i,j)==1]=φ(i)
所以: i=1∑nj=1∑n[gcd(i,j)==1]=2i=1∑nφ(i)−1
所以:
i=1∑nj=1∑n[gcd(i,j)==1]=2S(n)−1
那现在要找 gcd(i,j)=d的个数就是求 ∑i=1n∑j=1n[gcd(i,j)==d]
因为如果: gcd(i,j)==d,那么 gcd(di,dj)=1
所以就有:
i=1∑nj=1∑n[gcd(i,j)==d]=i=1∑dnj=1∑dn[gcd(i,j)==1]=2⋅S(dn)−1
好有了公式 ans=d=1∑nd⋅(2⋅S(dn)−1)之后,貌似 d还是要从1遍历到n,还是至少都是线性复杂度啊
这里就用到了整除分块了, dn不变的这段区间,虽然 d会变,但是是公差为1的等差数列,所以这一段的d也能一口气计算,所以整个复杂度就低了
#include"bits/stdc++.h"
using namespace std;
const int maxn=1e6+5;
typedef long long LL;
const LL MOD=1e9+7;
const LL inv2=5e8+4;
bool vis[maxn];
LL phi[maxn],Sphi[maxn];//欧拉函数,欧拉函数前缀和
vector<int>prime;
map<LL,LL>Mp;
void PHI(int n)
{
memset(vis,1,sizeof(vis));
phi[1]=1;
Sphi[1]=1;
for(int i=2;i<=n;i++)
{
if(vis[i])
{
prime.push_back(i);
phi[i]=i-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=0;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
Sphi[i]=(Sphi[i-1]+phi[i])%MOD;
}
}
LL S(LL n)//计算欧拉函数前缀和
{
if(n<=maxn-5)return Sphi[n];
if(Mp[n])return Mp[n];
LL res=(n%MOD)*((n+1)%MOD)%MOD*inv2%MOD;
for(LL i=2,j;i<=n;i=j+1)
{
j=n/(n/i);
res-=(LL)(j-i+1)%MOD*S(n/i)%MOD;
res%=MOD;
}
res=(res+MOD)%MOD;
return Mp[n]=res;
}
LL calc(LL i,LL j)//计算从i到j的公差为1的等差数列的和
{
LL res=(i+j)%MOD*(j-i+1LL)%MOD*inv2%MOD;
return res%MOD;
}
int main()
{
PHI(maxn-5);
LL N;
while(cin>>N)
{
LL res=0;
for(LL d=1,j;d<=N;d=j+1)
{
j=N/(N/d);
res+=calc(d,j)*(2LL*S(N/d)-1)%MOD;
res%=MOD;
}
res=(res+MOD)%MOD;
cout<<res<<endl;
}
}