前置知识:一些数论函数,比如欧拉函数、莫比乌斯函数的一些性质,积性函数及性质,整除分块。
这里默认大家会前置知识,如果不会请自行学习。
之前尝试看过,结果后来都忘光了,于是还是决定应该写个学习笔记记录一下。
首先开始介绍莫比乌斯反演。
我们设
和
为定义在非负整数集合上的两个函数,并且满足下列条件:
。那么根据莫比乌斯反演,有
证明一会儿讲狄利克雷卷积之后再证。
当然,如果是 ,那么我们有
莫比乌斯反演经常用来解决这样一类问题:我们要求 ,但是 并不好求,但是我们可以找到一个函数 ,满足 或者 ,并且 比较容易求出来,那么我们就可以求出 ,然后利用反演求出 。具体的代码实现时通常是用到整除分块来做到 来算出结果。
这里要用到一些复杂度优秀的筛法来预处理一些积性函数的前缀和,通常是
函数。比较简单地,我们可以用线性筛求出
函数等积性函数,然后
求前缀和。
但是我们会发现有些题目是需要复杂度更优秀的筛法的。我暂时还不会洲阁筛和min_25筛,于是在这里介绍一下杜教筛。
杜教筛是一种利用狄利克雷卷积来求前缀和的算法,所以在介绍杜教筛之前我先来介绍一下狄利克雷卷积。
先介绍几种函数,以下函数都是积性函数。
:表示
的约数个数。
,
应该是表示内部如果是true的话就是1,否则就是0的样子。
:
:恒等函数,值始终是1
:单位函数,
。
和
就不介绍了,但是介绍两个重要的性质:
好,开始介绍狄利克雷卷积。
两个数论函数
和
的卷积为
。前面的
表示卷积,后面的括号里的
是参数,通常不写,默认是
。
狄利克雷卷积满足以下运算法则:
1.交换律:
2.结合律:
3.分配律:
狄利克雷卷积的一个性质是两个积性函数的卷积仍然是一个积性函数。
我们介绍几个常见的卷积:
于是我们可以证明莫比乌斯反演了。
两边同时乘
得
由
得
其中
,而只有在
时,
的值是1,其他时候都是0,所以
。那么我们得到了:
带回狄利克雷卷积的式子,我们可得:
这样我们就证明了莫比乌斯反演。
接着我们要继续介绍杜教筛。
杜教筛是一种用低于线性的复杂度求一个积性函数前缀和的算法。
我们设我们要计算的函数是
,也就是求
,我们把这个前缀和记为
。
杜教筛要求我们构造两个函数
和
,使得
。我感觉这个构造也将成为杜教筛的一个难点吧(至少作为一个刚学的人来说是这么认为),其中我们也要求
和
的前缀和要好求。
于是开始推式子(我上网上抄的):
我们把第一项提出来,可以得到
移项可得:
我们快速求出
,然后对后面的部分整除分块,并且通常递归下去求更小的
,这样可以复杂度做到
,不会证明,因为证明要用到一些微积分的技巧。另外为了保证复杂度,我们应该预处理出前
。
那么我们这里讲两个常见的积性函数 和 的前缀和用杜教筛的求法。
首先先是
的前缀和的求法。
我们利用刚才的式子
,根据上文可知有
,而
的前缀和就是
,所以我们取
函数为我们构造用到的
函数。然后带回原式我们有
,于是我们可以用整除分块和递归来求出
的前缀和了。
下面是
的前缀和。
这个比较难想了,我们发现直接用
做
并不好做,于是我们用了
作
。
还是尝试构造
函数,我们发现这个
其实并不好办,于是我们想把他消掉,于是构造的
函数是
函数。我们考虑
的求法:
于是我们带回那个套路式:
其中前面的可以用平方的求和公式求出,后面的用等差数列和整除分块就可以了。
但是网上还有一种做法是只需要把莫比乌斯函数的式子变成
就可以了,我写的是这种做法。
代码是洛谷杜教筛模板的代码
#include <bits/stdc++.h>
using namespace std;
int T,vis[3000010],cnt,n;
long long ans,phi[3000010],mu[3000010],pri[3000010];
map<long long,long long> p,m;
inline void solve()
{
vis[1]=1;
mu[1]=1;
phi[1]=1;
for(int i=2;i<=3000000;++i)
{
if(!vis[i])
{
pri[++cnt]=i;
phi[i]=i-1;
mu[i]=-1;
}
for(int j=1;j<=cnt&&i*pri[j]<=3000000;++j)
{
vis[i*pri[j]]=1;
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
for(int i=1;i<=3000000;++i)
{
mu[i]=mu[i-1]+mu[i];
phi[i]=phi[i-1]+phi[i];
}
}
inline long long cal_mu(int x)
{
if(x<=3000000)
return mu[x];
if(m[x])
return m[x];
long long ans=1;
int r;
for(int l=2;l<=x;l=r+1)
{
r=x/(x/l);
ans-=(long long)(r-l+1)*cal_mu(x/l);
}
m[x]=ans;
return ans;
}
inline long long cal_phi(int x)
{
if(x<=3000000)
return phi[x];
if(p[x])
return p[x];
long long ans=(long long)x*(x+1)/2;
int r;
for(int l=2;l<=x;l=r+1)
{
r=x/(x/l);
ans-=(long long)(r-l+1)*cal_phi(x/l);
}
p[x]=ans;
return ans;
}
int main()
{
solve();
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
printf("%lld %lld\n",cal_phi(n),cal_mu(n));
}
return 0;
}