Preface
建议在认真学习并看懂了莫比乌斯反演入门再来学习本文。
本文重在讲解数论函数中常用的狄利克雷卷积以及OI中的神仙筛法杜教筛
还是参考和学习于peng-ym's blog
基础的基础——一些数论函数
关于数论函数
数论函数,是函数的一种废话。它的分类,性质,定义我们身为oier都不需要知道
常见的数论函数就像我们一直用的(phi,mu)这些东西。当然其它的也有很多。
关于积性函数
这个就了不得了,OI中所有的数论函数几乎都是积性函数。
它的定义就像它的名字一样,与乘积有关,在数学上严谨的定义为:
一个函数(f),满足(f(1)=1),且(f(pcdot q)=f(p)cdot f(q)(gcd(p,q)=1)),那么我们称这个函数为积性函数。
特殊的,当(gcd(p,q) ot=1)时也有上述性质的函数被称为完全积性函数
而传说中的杜教筛,就是用来筛积性函数的前缀和的一大法宝。
常见的积性函数
- (mu(n)) 即莫比乌斯函数,这个在莫比乌斯反演入门中已经讲的比较详细了,这里不再赘述。
- (phi(n)) 即欧拉函数,表示不大于(n)且与(n)互质的正整数个数,和(mu)一起常出现于各种反演之中。用数学式子表示为(phi(n)=sum_{i=1}^n[gcd(n,i)=1])
- (d(n)) 即约数个数函数,表示(n)的约数个数。用数学式子表示为(d(n)=sum_{d|n}1=sum_{d=1}^n[d|n])
- (sigma(n)) 即约数和函数,表示(n)的所有约数之和。用数学式子表示为(sigma(n)=sum_{d|n}d=sum_{d=1}^n [d|n]cdot d)
一般情况下,这些函数都可以通过欧拉筛(线性筛)在(O(n))的复杂度内筛出来。据说XYZ dalao会用欧拉筛筛(d(n)),反正我不会
常见的完全积性函数
- (epsilon(n)) 即元函数,表示为(epsilon(n)=[n=1])(和(mu)的那个性质有几分相似,等下我们就要分析它们之间的关系了)
- (I(n)) 即恒等函数,表示为(I(n)=1)(你没哟看错,不管怎样都等于(1))
- (id(n)) 即单位函数,表示为(id(n)=n)(看起来还是很傻吊)
这时候你可能就会产生和我一样naive的想法了,这些东西有个屁用。不过在接下来的狄利克雷卷积中,它们的作用就不只是一点两点了。
总结积性函数
积性函数有一个特别重要的性质,那就是积性函数(ast)积性函数((ast)即为等下要介绍的狄利克雷卷积)任然为积性函数。正因为有了这个性质因此才有了杜教筛。
狄利克雷卷积初步
关于狄利克雷卷积
这个东西听起来就散发着一股大佬的气息,所以会让人有点望而生畏
但是如果你明白并了解这它,其实也不是那么不可食用,反而会对你的推导过程起到良好的推动作用。
下面我们给出狄利克雷卷积的定义:两个数论函数(f)和(g)的卷积为((fast r)(n)=sum_{d|n} f(d)cdot g(frac{n}{d}))。
前面的括号表示将(f)卷(g),后面的括号表示范围(可以省略不写,一般默认为(n))
很显然,狄利克雷卷积作为一种运算符号,自然也有着关于它的运算律:
- 交换律:(fast g=gast f)
- 结合律:((fast g)ast h=fast(gast h))
- 分配律:((f+g)ast h=fast h+gast h)
我们可以认为和乘法的运算法则是基本一致的长的也差不多
然而这些看似显然的运算律都是可以证明的,例如交换律,其实就是交换了枚举的顺序,因为(f(d)cdot g(frac{n}{d})+f(frac{n}{d})cdot g(d)=f(frac{n}{d})cdot g(d)+f(d)cdot g(frac{n}{d})),因此存在交换律
了解这些之后,我们来看一看一些常见的数论函数与狄利克雷卷积的关系
(epsilon),元函数
看起来很智障的元函数在狄利克雷卷积中充当着单位元的作用。
关于单位元,可以于乘法中的1,矩阵中的单位矩阵(就是那个左上到右下的元素为(1),其它都为(0)的矩阵)进行类比。
它满足(fastepsilon=f),因为只有当(d=n)时,(epsilon(frac{n}{d})=epsilon(1)=1)使得(f=f(d)=f(n))
同样还是一句话,不要小看这个看似简单的东西,在某些方面可能可以展现出意想不到的效果。
(mu),莫比乌斯函数
我们知道,莫比乌斯函数有一个性质,就是(sum_{d|n}mu(d)=[n=1]),后面的那个形式是不是就是上面说的(epsilon)
再看前面的那个式子,和狄利克雷卷积的形式是不是有点相似除了没有另外一个函数和它卷
但是,我们考虑在这个式子中乘上一个(1),就变成(sum_{d|n}mu(d)cdot1),显然与原式是相等的。
考虑这个(1),那么根据我们之前介绍的那些完全积性函数,有一个恒等函数(I)与之对应。
因此我们有了一个在狄利克雷卷积中十分常用的恒等式:$$muast I=epsilon$$
还是感觉这个没什么用?我们可以用它来证明莫比乌斯反演定理:
对于:
我们用狄利克雷卷积的形式来表示这个式子就可以得到:(F=fast I)
我们在两边同时卷上一个(mu),就得到:
由于狄利克雷卷积具有结合律和交换律,因此有:
即(f=Fastmu)。把这个用狄利克雷卷积带进去就有(f(n)=sum_{d|n}mu(d)cdot F(frac{n}{d}))
同理也可以得到莫比乌斯反演定理的另一种形式(f(n)=sum_{n|d}mu(frac{d}{n})cdot F(d))
(phi),欧拉函数
关于欧拉函数,它有着一个十分重要的性质(这个相信大家都知道):(sum_{d|n}phi(d)=n)
和上面的方法类似,我们表示成狄利克雷卷积的形式为(muast I=id)
我们注意到这个式子和(mu)的那个式子里都有(I),所以我们考虑根据这个证明欧拉函数和莫比乌斯函数的关系,即对于:
我们在左右两边同时卷上一个(mu),得到:
因为(Iastmu=epsilon),即有:
即有:
把这个用狄利克雷卷积带进去就有:
我们将这是式子两边同时除以(n),即可得到那个关于欧拉函数和莫比乌斯函数的式子
浅谈杜教筛
关于杜教筛
首先,杜教筛为什么叫杜教筛?因为它是dls dalao发明的。
其次,杜教筛到底可以干什么?它可以用低于线性的复杂度(一般为(O(n^{frac{2}{3}})))筛出积性函数的前缀和。
杜教筛原理浅析
由于我们要求积性函数的前缀和,即我们需要计算(sum_{i=1}^n f(i))((f(i))为积性函数)
(接下来是杜教筛的套路,大家可理解可记忆)
为了解决这个问题,我们考虑借助强大的狄利克雷卷积,我们构造两个积性函数(h)和(g),使得(h=fast g)
我们先记(S(n)=sum_{i=1}^nf(i)),我们现在开始求(sum_{i=1}^n h(i))
转换下枚举项即有:
用(S(lfloorfrac{n}{d} floor))代替(sum_{i=1}^{lfloorfrac{n}{d} floor}f(i))得到:
由于我们要筛(S(n)),所以我们把右边式子的第一项提出来就有:
移给项即有:
其中的(h(i)=(fast g)(i))
这个就是杜教筛的常用套路了,如果(h)的前缀和可以用较快(小于等于(O(sqrt n)))的时间求出,那么我们对后面的式子再做一个除法分块就可以让复杂度达到(O(n^{frac{2}{3}}))
这就是传说中的杜教筛了,而实现时通常将小范围的用欧拉筛筛出,然后使用递归的方式(需要记忆化,用个map记一下)算较大的范围。
因此理论上只要我们找到两个函数使得(h=fast g),那么都是可以尝试用杜教筛求解的
不过这只是理论上的想法,我们还是看几个例子来熟悉一下这个套路。
杜教筛筛(mu)的前缀和
如果你认真看完上面的内容,那么这个显然是随便秒的。根据上面的套路:
我们现在要求的就是一个积性函数(g)使得(gast mu)的前缀和容易求。
根据狄利克雷卷积的(muast I=epsilon),我们把这个带进去就有:
所以对于后面的(S(lfloorfrac{n}{d} floor))再整除分块一下就可以求了。
杜教筛筛(phi)的前缀和
还是上面的套路式,我们想到(phiast I=id),带进去就有:
前面的(sum_{i=1}^n i)当然很好求,因此我们也筛出了欧拉函数的前缀和。
杜教筛的具体代码实现
这里直接上Luogu的板子题了:Luogu P4213 【模板】杜教筛(Sum)
方法大体就是上面说的那样,根据杜教筛的复杂度证明,我们要先用欧拉筛筛出(sqrt N)范围内的(mu)和(phi)的前缀和,但是实际应用中一般如果数组开的下的话都会开到(5 imes 10^6)或(10^7)
然后就是筛大于阈值的数时需要存储之前的答案,这里如果你勤快可以手写Hash,或者像我一样懒就写map不过理论多一个(log)
CODE
#include<cstdio>
#include<map>
#define RI register int
using namespace std;
typedef long long LL;
const int P=5000000;
map <int,int> sum_mu; map <int,LL> sum_phi;
int t,n,prime[P+5],mu[P+5],cnt; long long phi[P+5]; bool vis[P+5];
#define Pi prime[j]
inline void Euler(void)
{
vis[1]=mu[1]=phi[1]=1; RI i,j; for (i=2;i<=P;++i)
{
if (!vis[i]) prime[++cnt]=i,mu[i]=-1,phi[i]=i-1;
for (j=1;j<=cnt&&i*Pi<=P;++j)
{
vis[i*Pi]=1; if (i%Pi==0) { phi[i*Pi]=phi[i]*Pi; break; }
else mu[i*Pi]=-mu[i],phi[i*Pi]=phi[i]*(Pi-1);
}
}
for (i=1;i<=P;++i) mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}
#undef Pi
inline int Sum_mu(int x)
{
if (x<=P) return mu[x]; if (sum_mu.count(x)) return sum_mu[x];
int ans=1; for (RI l=2,r;l<=x;l=r+1)
{
r=x/(x/l); ans-=(r-l+1)*Sum_mu(x/l);
}
return sum_mu[x]=ans;
}
inline LL Sum_phi(int x)
{
if (x<=P) return phi[x]; if (sum_phi.count(x)) return sum_phi[x];
LL ans=1LL*x*(x+1)/2LL; for (RI l=2,r;l<=x;l=r+1)
{
r=x/(x/l); ans-=1LL*(r-l+1)*Sum_phi(x/l);
}
return sum_phi[x]=ans;
}
int main()
{
for (Euler(),scanf("%d",&t);t;--t)
scanf("%d",&n),printf("%lld %d
",Sum_phi(n),Sum_mu(n));
return 0;
}
Postscript
当然杜教筛的作用不知在于筛这些一般数论函数的前缀和。
一些其他的诸如(sum_{i=1}^nicdotmu(i))和(sum_{i=1}^ni^2cdotmu(i))都是可以筛的。
注意一般配的时候先把那些特殊性质不明显的数弄掉,然后猜积性函数。
这里的选择一般都要根据经验的积累和运气的使然
同时杜教筛的意义并不只在于筛积性函数前缀和,它的那种神奇的推式子的方法还是十分值得学习的。