杜教筛
积性函数:
对于任意互质的整数 \(a,b\) 有 \(f(ab)=f(a)f(b)\)
- 常见的积性函数:\(\varphi,\mu,\sigma,d\)
- 常见的完全积性函数:\(\epsilon,I,id\)
其中\(\epsilon(n)=[n=1],I(n)=1,id(n)=n\)
狄利克雷卷积:
设\(f, g\) 是两个数论函数,它们的狄利克雷卷积卷积是:\((f*g)(n)=\sum_{d|n}f(d)g(\frac{n}{d})\)
性质:
- \(\mu*I=\epsilon\)
- \(\varphi*I=id\)
- \(\mu*id=\varphi\)
- \(I*I=d \ [d(n)=\sum_{i|n}1]\)
- \(id*I=\sigma\ [\sigma(n)=\sum_{i|n}i]\)
杜教筛:
设现在要求积性函数 \(f\) 的前缀和:\(\sum_{i=1}^nf(n)=S(n)\)
再找一个积性函数\(g\),则考虑它们的狄利克雷卷积的前缀和:
\[\begin{aligned}
&\sum_{i=1}^n(f*g)\\
=&\sum_{i=1}^n\sum_{d|i}f(d)g(\frac{i}{d})\\
=&\sum_{d=1}g(d)\sum_{i=1}^{\frac{n}{d}}f(i)\\
=&\sum_{d=1}g(d)S(\lfloor\frac{n}{d}\rfloor)
\end{aligned}
\]
通过一系列运算,我们可以得到杜教筛的核心公式:
\[S(n)=g(1)S(n)=\sum_{i=1}^n(f*g)-\sum_{i=2}g(i)S(\lfloor\frac{n}{i}\rfloor)
\]
所以我们只要构造出\(g\)能够快速求得\(\sum_{i=1}^n(f*g)\)和\(g\)的前缀和,就能用数论分块递归求解。
我们只要先打表打出\(n^{\frac{2}{3}}\)的答案,中间的结果也可以用unordered_map记忆化,然后再利用递归求解。那么复杂度为\(O(n^{\frac{2}{3}})\)。
ll Sphi(int n){
if(n <= N) return phi[n]; //打表打出了
if(mphi.count(n)) return mphi[n]; //记忆化
ll ans = ...
for(int l = 2, r; l <= n; l = r + 1){
r = n / (n / l);
//ans ...
}
return mphi[n] = ans;
}
考虑到上面的求和过程中出现的都是 \(\lfloor \frac{n}{i} \rfloor\) 。开一个大小为两倍 \(\sqrt n\)n的数组 \(dp\) 记录答案。如果现在需要求出 \(GetSum(x)\) ,若 \(x \leq \sqrt n\) ,返回 \(dp[x]\),否则返回 \(dp[\sqrt n + n / i]\)即可。这样可以省去哈希表的复杂度。
这种做法可以把未打表的值直接映射出来
int n, up;
int N = 6000000;
ll mphi[100000]; //保存中间值
ll getans(int x){ //已经打表或记录中间值
return x <= N? mu[x] : mmu[up / x];
}
void solve(int n){
int t = up / n;
if(n <= N) return;
if(vis2[t]) return; //已经hash出来了
vis2[t] = 1;
mphi[t] = ...; //初始值
for(int l = 2, r; l <= n; l = r + 1){
r = n / (n / l);
solve(n / l); //开始记录
mphi[t] -= (r - l + 1) * getans(n / l);
}
}
int main(){
init(N);
int T;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
up = n;
if(n <= N) printf("%lld\n", phi[n]); //预处理的
else{
memset(vis2, 0, sizeof(vis2));
solve(n);
printf("%lld\n", mphi[1]); //最终答案为第1个
}
}
return 0;
}