正题
当我们要求一个这样的东西的时候,我们就会很困惑。
或者
。
记。
因为我们知道,也就是
。(这条式子的证明看数论函数的介绍)
所以可以得到两条式子。
,至于第一个等号,解释是,对于一个i要产生贡献,必定是与那些相乘小于等于n的j,所以产生的总贡献就是这样子的,然后第二个等号就很显然了,直接替换F。
所以
令i等于1时,变形可以得到
然后,整除分块递归+记忆化搜索就行。
接着我们来说一说怎么求
还是设。
因为有
所以得出两条式子:
两式相等,所以
那么
也是递归,整除分块即可。
可以通过复杂的证明得到,上述递归算法的时间复杂度是,那么就可以处理一个较大的n(约为int)的求和。
优化
其实是可以优化的,就是对于一个bound以下的数直接线性筛预处理,然后如果当前n<=bound,那么直接返回结果。
否则计算答案记录下之后再返回结果,又通过一系列复杂的证明可以知道,当时,有最优时间复杂度
。
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<map>
using namespace std;
const int bound=5e6;
map<int , long long> summ,sump;//sum of \mu and sum of \varphi
long long sum1[bound+10],sum2[bound+10];
int p[bound+10],temp;
bool vis[bound+10];
long long get_sum1(int x){
if(x<=bound) return sum1[x];
if(summ[x]) return summ[x];
int l=2,r;
long long ans=1;
while(l<=x){
r=x/(x/l);
ans-=(r-l+1)*get_sum1(x/l);
l=r+1;
}
return summ[x]=ans;
}
long long get_sum2(int x){
if(x<=bound) return sum2[x];
if(sump[x]) return sump[x];
int l=2,r;
long long ans=(long long)(x+1)*x/2;
while(l<=x){
r=x/(x/l);
ans-=(r-l+1)*get_sum2(x/l);
l=r+1;
}
return sump[x]=ans;
}
int main(){
sum1[1]=sum2[1]=1;
for(int i=2;i<=bound;i++){
if(!vis[i]) {p[++p[0]]=i;sum1[i]=-1;sum2[i]=i-1;}
for(int j=1;j<=p[0] && (temp=i*p[j])<=bound;j++){
vis[temp]=true;
if(i%p[j]==0) {sum2[temp]=sum2[i]*p[j];break;}
sum1[temp]=-sum1[i];
sum2[temp]=sum2[i]*(p[j]-1);
}
sum1[i]+=sum1[i-1];
sum2[i]+=sum2[i-1];
}
static int T,n;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
printf("%lld %lld\n",get_sum2(n),get_sum1(n));
}
}
进阶
假设我们现在要求呢?
上面的方法好像就行不通了。
考虑令这里指的是乘法。
那么问题变为求。
我们令
又因为。
所以左右两边同时卷积起来得到。
注意,这里不是,而是
,因为
是一个完全积性函数,两个相卷就相当于将
提出来。
然后再令
所以
等于
所以。
移项得到:。
然后和上面就一样了。
发现没有,我们处理一个熟悉的数论函数前面乘上了一个完全积性函数来求和的时候,我们可以通过这种构造方法来使得和很好求。