杜教筛入门
其实是因为莫反的题非常非常要用这个所以才来学。
有些莫反甚至要求灵活运用,而不只是求\(\sum\mu(n)\)和\(\sum\phi(n)\)
前置的芝士
狄利克雷卷积
对于两个数论函数\(f,g\),他们两个函数的前\(n\)项的狄利克雷卷积表示为\((f*g)(n)\),\((f*g)(n)=\displaystyle\sum_{d|n}f(d)g(\frac{n}{d})\)
显然狄利克雷卷积的运算满足交换律。同时满足结合律。
其单位元为\(\epsilon\),也就是满足\(f*\epsilon=f\),应该是很明显的吧。
额,\(\epsilon(n)=[n=1]\),还有后面会用到的\(I(n)=1\),\(id(n)=n\)
结合狄利克雷卷积能够得到一些非常非常有用的东西
1.\(\mu *I=\epsilon\)
2.\(\phi *I =id\)
3.\(\mu *id =\phi\)
这三个其实就分别对应了莫反最基础的三个公式
\(\epsilon(n)=\displaystyle\sum_{d|n}\mu(d) , n=\displaystyle\sum_{d|n}\phi(d) ,\sigma(n)=\displaystyle\sum_{d|n}d\)
后面推导的过程就会有体现。
莫比乌斯反演
若\(g(n)=\displaystyle\sum_{d|n}f(d)\),则有\(f(n)=\displaystyle\sum_{d|n}\mu(d)g(\frac{n}{d})\)
其实给出的条件就等价于\(g=f*I\),然后左右两边同时和\(\mu\)做卷积,得到\(g*\mu=f*I*\mu\),而根据上面所说的,\(I*\mu=\epsilon\)
所以能够得到\(g*\mu=f*\epsilon=f\),就是\(f(n)=\displaystyle\sum_{d|n}\mu(d)g(\frac{n}{d})\)
杜教筛
基本模型
考虑求一个积性函数\(f\)的前\(n\)项和\(S(n)=\displaystyle\sum_{i=1}^{n}f(i)\),设\(g\)为另一个积性函数
它们的狄利克雷卷积的前缀和为\(\displaystyle\sum_{i=1}^{n}(f*g)(i)=\displaystyle\sum_{i=1}^n\displaystyle\sum_{d|i}^{}f(d)g(\frac i d)\),然后是经典的反演思路,枚举\(d\)并且提前循环
得到\(\displaystyle\sum_{d=1}^nf(d)\displaystyle\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}g(i)=\displaystyle\sum_{d=1}^ng(d)\displaystyle\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)=\displaystyle\sum_{d=1}^ng(d)S({\lfloor\frac{n}{d}\rfloor})\)
而此时可以得到\(g(1)S(n)+\displaystyle\sum_{d=2}^ng(d)S({\lfloor\frac{n}{d}\rfloor})=\displaystyle\sum_{i=1}^n(f*g)(i)\)
再移项,就能够得到杜教筛的核心式子,\(g(1)S(n)=\displaystyle\sum_{i=1}^n(f*g)(i)-\displaystyle\sum_{d=2}^ng(d)S({\lfloor\frac{n}{d}\rfloor})\)
这个式子的作用在于,我们可以通过找到一个恰当的积性函数\(g\),使得\(\displaystyle\sum_{i=1}^n(f*g)(i)\)和\(\displaystyle\sum_{i=1}^n g\)能够被快速求解,我们便能够通过整数分块快速的得到\(S(n)\)的值,并且是在一个小于\(O(n)\)的时间复杂度内。
这个放一个伪代码来更好的展示思路。
ll Get_sum(int n)//用以计算某个积性函数f的前缀和
{
ll ans=fg_sum(n)//快速计算(f*g)(i)的前缀和
for(ll l=2,r;l<=n;l=r+1)//数论分块,因为后面的部分是使用数论分块求解的
{
r=n/(n/l);
ans-=(g_sum(r)-g_sum(l-1))*(Get_sum(n/l));//就是公式
}
return ans;
}
可以用记忆化优化,还有一些其他细节需要加上来优化常数,比如先用普通线性筛法筛出前面一部分的函数值,以避免在n较小的部分多次递归导致效率降低
例题
给定\(n\),求\(\sum \mu(i)\)和\(\sum \phi(i)\)
就是杜教筛最常用的两个板子,求欧拉函数和莫比乌斯函数
先考虑如何求\(\sum\mu(i)\),根据上面所说的性质,\(\mu * I =\epsilon\),需要\(\displaystyle\sum_{i=1}^n(f*g)(i)\)和\(\displaystyle\sum_{i=1}^n g\)能够被快速求解,\(\displaystyle\sum_{i=1}^n\epsilon\)和\(\displaystyle\sum_{i=1}^n I\)实在太明显,所以直接就取$f \(为\)\mu\(,\)g\(为\)I$
如何是如何求\(\sum\phi(i)\),和前面的差不多,只不过用的是\(\phi*I=id\),\(\displaystyle\sum_{i=1}^{n}id\)和\(\displaystyle\sum_{i=1}^{n}I\)也是非常好算,所以取\(f\)为\(\phi\),\(g\)为\(I\)
然后就是套上面的代码了,但是需要一点优化,否则常数还是在的,容易T。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
const ll N=5e6;
ll phi[N+1],prim[N+1],cnt,mu[N+1];
unordered_map<ll,ll> ans_mu,ans_phi;
ll vis[N+1];
ll Sum_mu(ll n)
{
if(n<=N)return mu[n];
if(ans_mu[n])return ans_mu[n];
ll ans=1;
for(ll l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
ans-=1LL*(r-l+1)*(Sum_mu(n/l));
}
return ans_mu[n]=ans;
}
ll Sum_phi(ll n)
{
if(n<=N)return phi[n];
if(ans_phi[n])return ans_phi[n];
ll ans=(n+1)*(n)/2;
for(ll l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
ans-=(r-l+1)*Sum_phi(n/l);
}
return ans_phi[n]=ans;
}
int main()
{
mu[1]=phi[1]=1;//先用筛法算出较小的部分
for(ll i=2;i<=N;i++)
{
if(vis[i]==0)prim[++cnt]=i,mu[i]=-1,phi[i]=i-1;
for(ll j=1;j<=cnt&&prim[j]*i<=N;j++)
{
vis[prim[j]*i]=1;
if(i%prim[j]==0)
{
phi[i*prim[j]]=phi[i]*prim[j];
mu[i*prim[j]]=0;
break;
}
phi[i*prim[j]]=phi[i]*phi[prim[j]];
mu[i*prim[j]]=mu[i]*mu[prim[j]];
}
}
for(ll i=1;i<=N;i++)mu[i]+=mu[i-1],phi[i]+=phi[i-1];
ll T=read();
while(T--)
{
ll n=read();
cout<<Sum_phi(n)<<' '<<Sum_mu(n)<<endl;
}
return 0;
}