杜教筛
作用
求部分积性函数前缀和,将一个不太好求的前缀和转化为两个比较好求的前缀和。
式子
\[S(n)=\sum_{i=1}^n f(i)
\]
\[g(1)S(n)=\sum_{i=1}^ng(i)S(\lfloor\frac ni\rfloor)-\sum_{i=2}^ng(i)S(\lfloor\frac ni\rfloor)
\]
\[\because \sum_{i=1}^n(f*g)(i)=\sum_{i=1}^n\sum_{d|i}f(d)g(\frac id)=\sum_{d=1}^ng(d)\sum_{i=1}^{\lfloor\frac nd\rfloor}f(i)=\sum_{d=1}^ng(d)S(\lfloor\frac nd\rfloor)
\]
\[\therefore g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\lfloor\frac ni\rfloor)
\]
过程
找到一个合适的积性函数 \(g\),使得能够快速求出 \(\sum g(i)\) 和 \(\sum(f*g)(i)\),那么就可以套用上述式子求出 \(f\) 的前缀和。复杂度 \(O(n^{\frac23})\)。
所以需要记住一些狄利克雷卷积关系
- \(\mu*I=\epsilon\)
- \(\mu*id=\phi\)
- \(\phi*I=id\)
例子
\(\sum\mu(i)\)
找到 \(\mu*I=\epsilon\),其中 \(\sum I(i)=n\),\(\sum\epsilon(i)=1\)。
\(\sum\phi(i)\)
找到 \(\phi*I=id\),其中 \(\sum I(i)=n\),\(\sum id=\frac{n(n+1)}2\)
代码
求上述两个东西。
注意我们要保存 \(\lfloor\frac ni\rfloor\) 这些位置的 \(S\),所以要建立一个映射关系
\[i\to\begin{cases}i&i\le\sqrt n\\\sqrt n+\lfloor\frac ni\rfloor&i>\sqrt n\end{cases}
\]
所以数组只用开 \(2\sqrt n\)。
实际上小于1e7的部分可以线性筛
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void Read(T &n){
char ch; bool flag=false;
while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
if(flag)n=-n;
}
#define int long long
typedef long long ll;
enum{
MAXN = 10000000,
M = 46341
};
int prime[MAXN], pnum;
ll mu[MAXN+1], phi[MAXN+1];
char check[MAXN+1];
inline void Sieve(int n){
for(int i=2; i<=n; i++){
if(!check[i]) prime[++pnum] = i, mu[i] = -1, phi[i] = i-1;
for(int j=1; i*prime[j]<=n and j<=pnum; j++){
int nxt = i*prime[j];
check[nxt] = true;
if(i%prime[j]==0){
phi[nxt] = phi[i]*prime[j];
break;
}
else
phi[nxt] = phi[i]*(prime[j]-1),
mu[nxt] = -mu[i];
}
}
mu[1] = phi[1] = 1;
for(int i=1; i<=n; i++) mu[i] += mu[i-1], phi[i] += phi[i-1];
}
int n;
ll smu[M+1], sphi[M+1];
char vis[M+1];
ll getmu(int x){
if(x<=MAXN) return mu[x];
if(vis[n/x]) return smu[n/x];
vis[n/x] = true;
ll &res = smu[n/x] = 1;
for(int l=2, r; l<=x; l=r+1){
r = x/(x/l);
res -= 1ll*(r-l+1)*getmu(x/l);
}
return res;
}
ll getphi(int x){
if(x<=MAXN) return phi[x];
if(vis[n/x]) return sphi[n/x];
vis[n/x] = true;
ll &res = sphi[n/x] = 1ll*x*(x+1)/2;
for(int l=2, r; l<=x; l=r+1){
r = x/(x/l);
res -= 1ll*(r-l+1)*getphi(x/l);
}
return res;
}
signed main(){
Sieve(MAXN);
int T; Read(T);
while(T--){
Read(n);
memset(vis,false,sizeof vis);
printf("%lld ",getphi(n));
memset(vis,false,sizeof vis);
printf("%lld\n",getmu(n));
}
return 0;
}