杜教晒
一、杜教晒的简介
杜教筛是用来在低于线性时间里,高效求一些积性函数的前缀和
杜教筛算法=整除分块 + 狄利克雷卷积是 + 线性筛
二、杜教筛公式的推导
杜教筛要解决的是这样一类问题:设 \(f(n)\) 是一个数论函数,计算 \(S(n) = \sum_{i = 1} ^ nf(i)\)
由于 \(n\) 很大,要求低于 \(O(n)\) 的复杂度求解, 所以用线性筛是不够的,如果能用到整除分块,每一块的值相等一起算,就加快了速度,也就是构造形如 \(\sum_{n = 1} ^ n {\lfloor {n \over i} \rfloor}\) 的整除分块
杜教筛的思路,简单的说就是把 \(f(n)\) 构造乘能利用整除分块的新的函数,这个构造用到了卷积。
记 \(S(n) = \sum_{i = 1} ^ nf(i)\) ,根据 \(f(n)\) 的性质,构造一个 \(S(n)\) 关于 \(S({n \over i})\) 的递推式, 下面是构造方法。
构造两个积性函数 \(h\) 和 \(g\) , 满足卷积: \(h = g * f\)。
根据卷积的定义, 有 $h(i) = \sum_{d | i}g(d)f({i \over d})$, 求和:
所以:
上面的式子就是杜教筛公式。
三、例子
莫比乌斯函数的前缀和, 记为 $ S(n) = \sum_{i = 1} ^n \mu(i) $
因为 \(\sum_{d | m} \mu(d) = [m = 1]\)
所以令 $ f = \mu, h = [m=1], g = 1 $, 则
代码:
#include<cstdio>
#include<map>
using namespace std;
const int N = 6e6 + 10;
typedef long long ll;
map<int, int> mp;
int mu[N], prime[N], cnt;
bool vis[N];
void getmu() {
mu[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) {
prime[cnt++] = i;
mu[i] = -1;
}
for(int j = 0; j < cnt && i * prime[j] < N; j++) {
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for(int i = 1; i < N; i++) {
mu[i] += mu[i - 1];
}
}
ll calsmu(ll x) {
if(x < N) return mu[x];
if(mp[x]) return mp[x];
ll ans = 1, r = 0;
for(ll i = 2; i <= x; i = r + 1) {
r = x / (x / i);
ans -= (r - i + 1) * calsmu(x / i);
}
return mp[x] = ans;
}
int main() {
getmu();
ll a, b;
scanf("%lld%lld", &a, &b);
printf("%lld\n", calsmu(b) - calsmu(a - 1));
return 0;
}
欧拉函数前缀和, 记为 $ S(n) = \sum_{i = 1} ^ n \varphi(i) $
因为 $ \sum_{d|n} ^ n \varphi(d) = n $
所以令 $ f = \varphi,h = i, g = 1$, 则
代码:
#include<cstdio>
#include<map>
using namespace std;
typedef long long ll;
const int N = 6e6 + 10;
const ll mod = 1000000007;
ll prime[N], cnt, phi[N];
bool vis[N];
map<ll, ll> mp;
const ll inv=500000004;
void getphi() {
phi[1] = 1;
for(int i = 2; i < N; i++) {
if(!vis[i]) {
prime[cnt++] = i;
phi[i] = i - 1;
}
for(int j = 0; j < cnt && i * prime[j] < N; j++) {
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * phi[prime[j]];
}
}
for(int i = 1; i < N; i++) {
phi[i] = (phi[i] + phi[i - 1]) % mod;
}
}
ll calsphi(ll n) {
if(n < N) return phi[n];
if(mp[n]) return mp[n];
ll ans = n % mod * (n % mod + 1ll) % mod * inv % mod;
ll r = 0;
for(ll i = 2; i <= n; i = r + 1) {
r = n / (n / i);
ans = (ans - (r - i + 1) % mod * calsphi(n / i) % mod + mod) % mod;
}
return mp[n] = ans % mod;
}
int main() {
getphi();
ll n;
scanf("%lld", &n);
printf("%lld\n", calsphi(n));
return 0;
}
四、例题
1.function
题目描述
There is a function \(f(x)\),which is defined on the natural numbers set \(N\) ,satisfies the following eqaution $ N^2 - 3 * N + 2 = \sum_{d|N}f(d) $
calulate \(\sum_{i = 1} ^ Nf(i)\ \ \ \ mod \ \ \ 10^9 + 7\).
输入描述
the first line contains a positive integer \(T\),means the number of the test cases.
next \(T\) lines there is a number \(N\)
\(T \leq 500, N \leq 10^9\)
only \(5\) test cases has \(N > 10^6\)
输出描述
\(T\)lines,each line contains a number,means the answer to the \(i\)-th test case.
Sample input
1
3
Sample Output
2
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5608
解题思路:
这题就是杜教筛的模板题
题目中给定$ N^2 - 3 * N + 2 = \sum_{d|N}f(d) $
所以令 \(h = i^2-3*i+2, g = 1\) 即可
这题你通过题目中给定的式子可以发现 \(f(i) = h(i) - \sum_{d|i}h(d)\)
因此当n很小的时候可以先离线计算
AC代码:
#include<cstdio>
#include<map>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
const int mod = 1e9 + 7;
ll ans[N], F[N];
ll inv3 = 333333336;
void init() {
for(int i = 1; i < N; i++) {
ans[i] = 1ll * (i - 1) * (i - 2) % mod;
}
for(int i = 1; i < N; i++) {
for(int j = 2 * i; j < N; j += i) {
ans[j] = (ans[j] - ans[i] + mod) % mod;
}
}
for(int i = 2; i < N; i++) {
ans[i] = (ans[i] + ans[i - 1]) % mod;
}
}
map<ll, ll> mp;
ll getsf(ll n) {
if(n < N) return ans[n];
if(mp[n]) return mp[n];
ll ans = (n % mod) * (n - 1) % mod * (n - 2) % mod * inv3 % mod, r = 0;
for(ll i = 2; i <= n; i = r + 1) {
r = n / (n / i);
ans = (ans - (r - i + 1) % mod * getsf(n / i) % mod + mod) % mod;
}
return mp[n] = ans;
}
int main() {
init();
int t;
ll n;
scanf("%d", &t);
while(t--) {
scanf("%lld", &n);
printf("%lld\n", getsf(n));
}
return 0;
}

浙公网安备 33010602011771号