杜教篩學習筆記
前置知識
莫比烏斯反演
數論函式
積性函式
若 \(gcd(a,b)=1\),則\(f(a \times b)=f(a) \times f(b)\)
完全積性函式
去掉 \(gcd(a,b)=1\) 的條件
常見的積性函式
恆等函式:\(I(n)=1\)
單位函式:\(id(n)=n\)
元函式:\(\epsilon(n)=[n=1]\)
(以上三個函式也是完全積性函式)
尤拉函式:\(\varphi(n)\):小於 \(n\) 和 \(n\) 互質的自然數個數
莫比烏斯函式:\(\mu(n)\)
約數和函式:\(\sigma_k(n)\) 表示 \(n\) 的所有因數的 \(k\)
狄利克雷卷積
定義
兩個函式 \((f,g)\) 的狄利克雷卷積記為 \(f*g\)
\((f * g)(n)=\sum_{k \mid n} f(k) \times g\left(\frac{n}{k}\right)\)
性質
\(1\)、交換律 \(f*g=g*f\)
\(2\)、結合律 \(f*(g*h)=(f*g)*h\)
\(3\)、分配律 \(f*h+g*h=(f+g)*h\)
\(4\)、若 \(f,g\) 為積性函式,則 \(f*g\) 也是積性函式
常用卷積式
\(1\)、\(\mu*I=\epsilon\)
即 \(\sum_{d|n}\mu(d)=[n=1]\)
也就是莫比烏斯函式性質一
\(2\)、\(\mu*id=\varphi\)
即 \(\sum_{d|n}\mu(d)\frac{n}{d}=\varphi(n)\)
也就是莫比烏斯函式性質二
\(3\)、\(\varphi * I=id\)
即 \(\sum_{d|n}\varphi(d)=n\)
杜教篩
可以在低於線性的時間內篩出積性函式的字首和
如果我們要篩的積性函式為 \(f\)
那麼杜教篩的核心就是構造兩個積性函式\(h,g\),使得 \(h=f*g\)
並且 \(h\) 和 \(g\) 的字首和能夠快速地求出來
首先我們要用線性篩篩出一部分函式值,之後遞迴的時候要用到
設 \(s(n)=\sum\limits_{i=1}^nf(i)\)
\(\begin{aligned} \sum\limits_{i=1}^n(f*g)(i) &=\sum\limits_{i=1}^n\sum_{d|i}f(d)g(\frac{d}{i}) \\&=\sum\limits_{d=1}^ng(d)\sum_{i=1}^{\frac{n}{d}} f(i)\\ &=\sum\limits_{d=1}^ng(d)s(\frac{n}{d}) \\ &=g(1)s(n)+\sum\limits_{d=2}^ng(d)s(\frac{n}{d}) \end{aligned}\)
所以
\(s(n)g(1)=\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{d=2}^ng(d)s(\frac{n}{d})\)
這樣,我們就得到了一個關於 \(s(n)\) 的表示式
前半部分是 \(h\) 函式的字首和,可以快速得到
後半部分可以進行整除分塊遞迴求解,遞迴的時候要記憶化
最後再整體除一個 \(g(1)\) 即可
時間複雜度 \(O(n^{\frac{2}{3}})\)
杜教篩能夠篩的積性函式不是很多
主要是配合莫比烏斯反演使用
篩 \(\mu\) 函式時利用 \(\mu*I=\epsilon\)
篩 \(\varphi\) 函式時利用 \(\varphi * I=id\)
因此需要掌握一些常見的字首和的公式
程式碼
#include<cstdio>
#include<iostream>
#include<map>
#include<cstring>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
typedef long long ll;
const int maxn=5e6+5,mmax=5e6,mod=1e6+3;
int pri[maxn];
ll mu[maxn],phi[maxn];
bool not_pri[maxn];
struct has{
struct asd{
int nxt,num;
ll val;
}b[maxn];
int h[maxn],tot;
has(){
memset(h,-1,sizeof(h));
tot=1;
}
void insert(rg int num,rg ll val){
rg int now=num%mod;
b[tot].nxt=h[now];
b[tot].val=val;
b[tot].num=num;
h[now]=tot++;
}
ll cx(rg int num){
rg int now=num%mod;
for(rg int i=h[now];i!=-1;i=b[i].nxt){
if(b[i].num==num) return b[i].val;
}
return -1;
}
}ans_phi,ans_mu;
void xxs(){
not_pri[0]=not_pri[1]=1;
mu[1]=phi[1]=1;
for(rg int i=2;i<=mmax;i++){
if(!not_pri[i]){
pri[++pri[0]]=i;
mu[i]=-1;
phi[i]=i-1;
}
for(rg int j=1;j<=pri[0] && 1LL*i*pri[j]<=mmax;j++){
not_pri[i*pri[j]]=1;
if(i%pri[j]==0){
phi[i*pri[j]]=phi[i]*pri[j];
mu[i*pri[j]]=0;
break;
} else {
phi[i*pri[j]]=phi[i]*phi[pri[j]];
mu[i*pri[j]]=-mu[i];
}
}
}
for(rg int i=1;i<=mmax;i++){
mu[i]+=mu[i-1];
phi[i]+=phi[i-1];
}
}
ll getsum_mu(rg int now){
if(now<=mmax) return mu[now];
if(ans_mu.cx(now)!=-1) return ans_mu.cx(now);
ll ans=1;
for(rg int l=2,r=0;r<now;l=r+1){
r=now/(now/l);
ans-=1LL*(r-l+1)*getsum_mu(now/l);
}
ans_mu.insert(now,ans);
return ans;
}
ll getsum_phi(rg int now){
if(now<=mmax) return phi[now];
if(ans_phi.cx(now)!=-1) return ans_phi.cx(now);
ll ans=1LL*now*(now+1LL)/2;
for(rg int l=2,r=0;r<now;l=r+1){
r=now/(now/l);
ans-=1LL*(r-l+1)*getsum_phi(now/l);
}
ans_phi.insert(now,ans);
return ans;
}
int t,n;
int main(){
xxs();
t=read();
while(t--){
n=read();
printf("%lld %lld\n",getsum_phi(n),getsum_mu(n));
}
return 0;
}