杜教筛学习笔记
ShiRoZeTsu · · 个人记录
杜教筛可以在亚线性时间求出一些数论函数的前缀和。
基本思路
比如我们要求出
我们考虑一个函数
所以有:
如果我们能快速求出
模板:P4213 【模板】杜教筛
给定正整数
n ,求\sum_{i=1}^n \mu(i) 和\sum_{i=1}^n \varphi(i) 。
对于
对于
但是也可以用莫比乌斯反演。还记得仪仗队这道题吗?我们有一个式子:
所以:
所以只要求出
用莫比乌斯反演:
所以也可以用
CODE:
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 5;
const int maxk = 2e6;
int tot;
int mu[maxn], prime[maxn>>1];
ll smu[maxn];
map<int, ll> mp;
bool not_prime[maxn];
void prework() {
mu[1] = 1;
for(int i = 2; i <= maxk; i++) {
if(!not_prime[i]) prime[++tot] = i, mu[i] = -1;
for(int j = 1; j <= tot && i*prime[j] <= maxk; j++) {
not_prime[i*prime[j]] = true;
if(i % prime[j] == 0) {
mu[i*prime[j]] = 0;
break;
}
mu[i*prime[j]] = -mu[i];
}
}
for(int i = 1; i <= maxk; i++)
smu[i] = smu[i-1] + mu[i];
}
ll summu(int n) {
if(n <= maxk) return smu[n];
if(mp[n]) return mp[n];
ll res = 1;
for(ll l = 2, r; l <= n; l = r+1) {
r = n/(n/l);
res -= summu(n/l) * (r-l+1);
}
return mp[n] = res;
}
ll sumphi(int n) {
ll res = 0;
for(ll l = 1, r; l <= n; l = r+1) {
r = n/(n/l);
res += (summu(r) - summu(l-1)) * (n/l) * (n/l);
}
return (res + 1) >> 1ll;
}
int main() {
prework();
int T, n;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
printf("%lld %lld\n", sumphi(n), summu(n));
}
return 0;
}