Label
杜教筛模板题
Description
T(T≤10)T(T\le 10)T(T≤10)组数据,每组数据包含一个正整数n(n<231)n(n<2^{31})n(n<231),求∑i=1nφ(i)\sum_{i=1}^{n}\varphi(i)∑i=1nφ(i)与∑i=1nμ(i)\sum_{i=1}^{n}\mu(i)∑i=1nμ(i)。
Solution
杜教筛
杜教筛一般用于处理数论函数前缀和的问题。杜教筛的基本思想是:对于求解某个数论函数f(n)f(n)f(n)前缀和(而非一个线性表)S(n)=∑i=1nf(i)S(n)=\sum_{i=1}^{n}f(i)S(n)=∑i=1nf(i),我们设法构造一个S(n)S(n)S(n)关于S(⌊ni⌋)S(\lfloor\frac{n}{i}\rfloor)S(⌊in⌋)的递推式。
引理:对于任意两个数论函数f,gf,gf,g,设S(n)=∑i=1nf(i)S(n)=\sum_{i=1}^{n}f(i)S(n)=∑i=1nf(i),则必有:
∑i=1n(f∗g)(i)=∑i=1ng(i)S(⌊ni⌋)\sum_{i=1}^{n}(f*g)(i)=\sum_{i=1}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)i=1∑n(f∗g)(i)=i=1∑ng(i)S(⌊in⌋)
证明:按照P3327题解注释(2)(3)的方法,我们不难得到:
∑i=1n(f∗g)(i)\sum_{i=1}^{n}(f*g)(i)i=1∑n(f∗g)(i)
=∑i=1n∑d∣ig(d)f(id)=\sum_{i=1}^{n}\sum_{d|i}g(d)f(\frac{i}{d})=i=1∑nd∣i∑g(d)f(di)
=∑d=1ng(d)∑i=1n[d∣i]f(id)=\sum_{d=1}^{n}g(d)\sum_{i=1}^{n}[d|i]f(\frac{i}{d})=d=1∑ng(d)i=1∑n[d∣i]f(di)
=∑d=1ng(d)∑i=1n[1∣id]f(id)=\sum_{d=1}^{n}g(d)\sum_{i=1}^{n}[1|\frac{i}{d}]f(\frac{i}{d})=d=1∑ng(d)i=1∑n[1∣di]f(di)
=∑d=1ng(d)∑i=1⌊nd⌋f(i)=\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)=d=1∑ng(d)i=1∑⌊dn⌋f(i)
=∑i=1ng(i)S(⌊ni⌋),□=\sum_{i=1}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor),\square=i=1∑ng(i)S(⌊in⌋),□
将引理∑i=1n∑d∣i(f∗g)(i)=∑i=1ng(i)S(⌊ni⌋)\sum_{i=1}^{n}\sum_{d|i}(f*g)(i)=\sum_{i=1}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)i=1∑nd∣i∑(f∗g)(i)=i=1∑ng(i)S(⌊in⌋)变形可得:
g(1)S(n)+∑i=2ng(i)S(⌊ni⌋)=∑i=1n(f∗g)(i)g(1)S(n)+\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)=\sum_{i=1}^{n}(f*g)(i)g(1)S(n)+i=2∑ng(i)S(⌊in⌋)=i=1∑n(f∗g)(i)
g(1)S(n)=∑i=1n(f∗g)(i)−∑i=2ng(i)S(⌊ni⌋)g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)g(1)S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋) (1)(1)(1)
根据式(1),我们可以构造如下求S(n)S(n)S(n)的方法:
考虑之前学过的常见积性函数间卷积运算转换的公式,我们构造合适的数论函数ggg进行构造,函数ggg显然需满足f∗gf*gf∗g为一个可以快捷求出前缀和(这样一来,如果我们可以快速求出∑i=1n(f∗g)(i)\sum_{i=1}^{n}(f*g)(i)∑i=1n(f∗g)(i))的函数且对于∀xg(x)\forall xg(x)∀xg(x)易求。一般情况下,我们构造g(n)=1g(n)=1g(n)=1。
这样一来,剩下的问题在于∑i=2ng(i)S(⌊ni⌋)\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)∑i=2ng(i)S(⌊in⌋)怎么求:显然,我们可利用数论分块求此式的值。至于求和涉及到的每一项里S(⌊ni⌋)S(\lfloor\frac{n}{i}\rfloor)S(⌊in⌋),我们可以再利用递推式(1)(1)(1)求出它的值。
假设实际应用中涉及到的数论函数的值的线性表均线性复杂度可求,一般根据此方法直接递归计算的时间复杂度为O(n34)O(n^{\frac{3}{4}})O(n43)。考虑先线性筛预处理得到SSS的前n23n^{\frac{2}{3}}n32项,剩余部分时间复杂度为O(∫on13nxdx)=O(n23)O(\int_{o}^{n^{\frac{1}{3}}}\sqrt\frac{n}{x}dx)=O(n^{\frac{2}{3}})O(∫on31xndx)=O(n32),故整体算法时间复杂度为O(n23)O(n^{\frac{2}{3}})O(n32)。
对于较大的SSS的值,由于不同值的S(⌊ni⌋)S(\lfloor\frac{n}{i}\rfloor)S(⌊in⌋)个数不会超过2n2\sqrt n2n个,故用map存下其对应的值较为简便。
求∑i=1nμ(i)\sum_{i=1}^{n}\mu(i)∑i=1nμ(i)
考虑公式μ∗1=ϵ\mu*1=\epsilonμ∗1=ϵ。
令f=μ,g=1f=\mu,g=1f=μ,g=1,则:
g(1)S(n)=∑i=1n(f∗g)(i)−∑i=2ng(i)S(⌊ni⌋)g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)g(1)S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋)
↔S(n)=∑i=1nϵ(i)−∑i=2nS(⌊ni⌋)=1−∑i=2nS(⌊ni⌋)\leftrightarrow S(n)=\sum_{i=1}^{n}\epsilon(i)-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor)=1-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor)↔S(n)=i=1∑nϵ(i)−i=2∑nS(⌊in⌋)=1−i=2∑nS(⌊in⌋)
算法时间复杂度为O(n23)O(n^{\frac{2}{3}})O(n32)。
求∑i=1nφ(i)\sum_{i=1}^{n}\varphi(i)∑i=1nφ(i)
考虑公式φ∗1=id\varphi*1=idφ∗1=id
令f=φ,g=1f=\varphi,g=1f=φ,g=1,则:
g(1)S(n)=∑i=1n(f∗g)(i)−∑i=2ng(i)S(⌊ni⌋)g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)g(1)S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋)
↔S(n)=∑i=1nid(i)−∑i=2nS(⌊ni⌋)=∑i=1ni−∑i=2nS(⌊ni⌋)=n(n+1)2−∑i=2nS(⌊ni⌋)\leftrightarrow S(n)=\sum_{i=1}^{n}id(i)-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor)=\sum_{i=1}^{n}i-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor)=\frac{n(n+1)}{2}-\sum_{i=2}^{n}S(\lfloor\frac{n}{i}\rfloor)↔S(n)=i=1∑nid(i)−i=2∑nS(⌊in⌋)=i=1∑ni−i=2∑nS(⌊in⌋)=2n(n+1)−i=2∑nS(⌊in⌋)
算法时间复杂度为O(n23)O(n^{\frac{2}{3}})O(n32)。
根据以上两个过程,不难看出选择合适的ggg与f∗gf*gf∗g的重要性。
Code
#include<cstdio>
#include<iostream>
#include<map>
#define ri register int
#define ll long long
using namespace std;
const int MAXN=1e6;
int T,cnt,prime[MAXN];
ll N,smu[MAXN+20],sphi[MAXN+20];
bool notprime[MAXN+20];
map<ll,ll>Smu;
map<ll,ll>Sphi;
void EulaSieve()
{
smu[1]=1,sphi[1]=1,notprime[1]=true;
for(ri i=2;i<=MAXN;++i)
{
if(!notprime[i]) prime[++cnt]=i,smu[i]=-1,sphi[i]=i-1;
for(ri j=1;j<=cnt&&i*prime[j]<=MAXN;++j)
{
notprime[i*prime[j]]=true;
if(i%prime[j]==0) sphi[i*prime[j]]=sphi[i]*prime[j];
else sphi[i*prime[j]]=sphi[i]*sphi[prime[j]];
if(i%prime[j]==0) break;
else smu[i*prime[j]]=-smu[i];
}
}
for(ri i=1;i<=MAXN;++i)
smu[i]=smu[i-1]+smu[i],sphi[i]=sphi[i-1]+sphi[i];
}
ll S_mu(ll n)
{
if(n<=MAXN) return smu[n];
if(Smu[n]) return Smu[n];
ll ans=1LL;
for(ll l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
ans-=(r-l+1)*S_mu(n/l);
}
return Smu[n]=ans;
}
ll S_phi(ll n)
{
if(n<=MAXN) return sphi[n];
if(Sphi[n]) return Sphi[n];
ll ans=n*(n+1)/2LL;
for(ll l=2,r;l<=n;l=r+1)
{
r=n/(n/l);
ans-=(r-l+1)*S_phi(n/l);
}
return Sphi[n]=ans;
}
int main()
{
std::ios::sync_with_stdio(false);
cin>>T;
EulaSieve();
for(ri op=1;op<=T;++op)
{
cin>>N;
cout<<S_phi(N)<<" "<<S_mu(N)<<'\n';
}
return 0;
}
本文详细介绍了杜教筛算法的原理及其在计算数论函数前缀和问题上的应用,包括如何利用该算法求解欧拉函数φ(n)和莫比乌斯函数μ(n)的前缀和,并提供了C++实现代码。通过数论分块和递推式,实现了O(n^{frac{2}
786

被折叠的 条评论
为什么被折叠?



