简单介绍
杜教筛被用于处理一类数论函数的前缀和问题。
对于数论函数 \(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;
}
这样你就通过了模板题!
实现细节与时间复杂度
以下作者都不知道咋证明
- 朴素杜教筛的时间复杂度是 \(O(\frac3 4)\)
- jing