2025ing

博客园 首页 新随笔 联系 订阅 管理

简单介绍

杜教筛被用于处理一类数论函数的前缀和问题。
对于数论函数 \(f\),杜教筛可以在低于线性时间的复杂度内计算 \(F(n)=\sum_{i=1}^{n}f(i)\)
想办法构造一个 \(F(n)\) 关于 \(F\left(\left\lfloor\frac{n}{i}\right\rfloor\right)\) 的递推式。

原理分析

对于任意一个数论函数 \(g\),必满足:

\[\begin{aligned} \sum_{i=1}^{n}(f * g)(i) & =\sum_{i=1}^{n}\sum_{d \mid i}g(d)f\left(\frac{i}{d}\right) \\ & i \to id \\ &=\sum_{d=1}^{n}g(d)\sum_{i=1}^{\frac{n}{d}}f(i) \\ &=\sum_{d=1}^{n}g(d)F(\frac{n}{d}) \\ &=g(1)F(n)+\sum_{d=2}^{n}g(d)F(\frac{n}{d}) \\ \end{aligned} \]

\[g(1)F(n)=\sum_{i=1}^{n}(f * g)(i)-\sum_{d=2}^{n}g(d)F(\frac{n}{d}) \]

\[F(n)=\frac{\sum_{i=1}^{n}(f * g)(i)-\sum_{i=2}^{n}g(i)F(\frac{n}{i})}{g(1)} \]

现在如果我们能较快的求出 \(f*g\)\(g\) 的前缀和
那我们就能用一些数论分块递归求出它的值
伪代码如下

F(n)
    ans=FGsum(n)
    for i 2->n
        r=n/(n/i)
        ans-=(Gsum(r)-Gsum(i-1))*F(n/i)
    return ans/G(1)

模板题 [P4213]

其中的两个公式见 莫反基础
然后我们知道 \(\mu*1=\epsilon\)\(\phi*1=id\)
然后我们两个函数直接代入 \(1\) 即可
\(\epsilon\)\(id\) 都很唐式不用管
\(1\) 的前缀和也是显然
所以这一道水紫就这么被水掉啦!
然而并没有
你写了像这样的一个代码
非常的简洁清晰

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll F_phi(int n){
	ll ans=(n+1)*n/2;
	for(int i=2,r;i<=n;i++){
		r=n/(n/i);
		ans-=(r-i+1)*F_phi(n/i);
		i=r;
	}
	return ans;
}
ll F_mu(int n){
	ll ans=1;
	for(int i=2,r;i<=n;i++){
		r=n/(n/i);
		ans-=(r-i+1)*F_mu(n/i);
		i=r;
	}
	return ans;
}
int main(){
	int T,n;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		printf("%lld %lld\n",F_phi(n),F_mu(n));
	}
	return 0;
}

交上去一发喜提 \(TLE\) 十分
因为朴素杜教筛的时间复杂度是 \(O(n^{\frac3 4})\)
这个东西如何优化呢
首先,这个东西当 \(n\) 很小的时候
他就会变得很唐
所以我们可以预处理出一部分函数值
这样他的时间复杂度就可以减少很多
然后你改了一波

#include<bits/stdc++.h>
using namespace std;
const int N=100009;
typedef long long ll;
ll mu[N+9],phi[N+9],prime[N+9],cnt=0;
bool st[N+9];
void Eular(){
	st[1]=true;
	mu[1]=phi[1]=1;
	for(int i=2;i<=N;i++){
		if(!st[i])prime[++cnt]=i,phi[i]=i-1,mu[i]=-1;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			st[i*prime[j]]=1;
			if(i%prime[j]==0){
				mu[i*prime[j]]=0;
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}else{
				phi[i*prime[j]]=phi[i]*phi[prime[j]];
				mu[i*prime[j]]=-mu[i];
			}
		}
	}
	for(int i=1;i<=N;i++)phi[i]+=phi[i-1],mu[i]+=mu[i-1];
}
ll F_phi(ll n){
	if(n<=N)return phi[n];
	ll ans=(n+1)*n/2;
	for(int i=2,r;i<=n;i++){
		r=n/(n/i);
		ans-=(r-i+1)*F_phi(n/i);
		i=r;
	}
	return ans;
}
ll F_mu(ll n){
	if(n<=N)return mu[n];
	ll ans=1;
	for(int i=2,r;i<=n;i++){
		r=n/(n/i);
		ans-=(r-i+1)*F_mu(n/i);
		i=r;
	}
	return ans;
}
int main(){
	Eular();
	int T;
	ll n;
	scanf("%d",&T);
	while(T--){
		scanf("%lld",&n);
		printf("%lld %lld\n",F_phi(n),F_mu(n));
	}
	return 0;
}

\(submit,TLE,30\)
本题因为他卡常所以我们需要拿 \(map\) 加一个记忆化
然后我们可以把数组开大一些
\(AC\) \(Code\)

#include<bits/stdc++.h>
using namespace std;
const int N=4000009;
typedef long long ll;
ll mu[N+9],phi[N+9],prime[N+9],cnt=0;
bool st[N+9];
void Eular(){
	st[1]=true;
	mu[1]=phi[1]=1;
	for(int i=2;i<=N;i++){
		if(!st[i])prime[++cnt]=i,phi[i]=i-1,mu[i]=-1;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			st[i*prime[j]]=1;
			if(i%prime[j]==0){
				mu[i*prime[j]]=0;
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}else{
				phi[i*prime[j]]=phi[i]*phi[prime[j]];
				mu[i*prime[j]]=-mu[i];
			}
		}
	}
	for(int i=1;i<=N;i++)phi[i]+=phi[i-1],mu[i]+=mu[i-1];
}
unordered_map<ll,ll> Phi,Mu;
ll F_phi(ll n){
	if(n<=N)return phi[n];
	if(Phi.count(n))return Phi[n];
	ll ans=(n+1)*n/2;
	for(ll i=2,r;i<=n;i=r+1){
		r=n/(n/i);
		ans-=(r-i+1)*F_phi(n/i);
	}
	return Phi[n]=ans;
}
ll F_mu(ll n){
	if(n<=N)return mu[n];
	if(Mu.count(n))return Mu[n];
	ll ans=1;
	for(ll i=2,r;i<=n;i=r+1){
		r=n/(n/i);
		ans-=(r-i+1)*F_mu(n/i);
	}
	return Mu[n]=ans;
}
int main(){
	Phi.clear(),Mu.clear();
	Eular();
	int T;
	ll n;
	scanf("%d",&T);
	while(T--){
		scanf("%lld",&n);
		printf("%lld %lld\n",F_phi(n),F_mu(n));
	}
	return 0;
}

这样你就通过了模板题!

实现细节与时间复杂度

以下作者都不知道咋证明

  1. 朴素杜教筛的时间复杂度是 \(O(\frac3 4)\)
  2. jing
posted on 2025-03-29 18:22  2025ing  阅读(6)  评论(0)    收藏  举报