杜教筛入门

其实是因为莫反的题非常非常要用这个所以才来学。
有些莫反甚至要求灵活运用,而不只是求\(\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较小的部分多次递归导致效率降低

例题

luogu p4213

给定\(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;
}
posted @ 2024-09-06 10:58  HL_ZZP  阅读(14)  评论(0编辑  收藏  举报