杜教晒

一、杜教晒的简介

​ 杜教筛是用来在低于线性时间里,高效求一些积性函数的前缀和

​ 杜教筛算法=整除分块 + 狄利克雷卷积是 + 线性筛

二、杜教筛公式的推导

​ 杜教筛要解决的是这样一类问题:设 \(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})$, 求和:

\[\sum_{i = 1} ^ nh(i) = \sum_{i = 1}^n\sum_{d|i}^ig(d)f({i \over d}) \\=\sum_{d = 1}^ng(d)\sum_{i =1}^{\lfloor {n \over d} \rfloor}f(i) \\=\sum_{d = 1} ^ ng(d)S({\lfloor {n \over d} \rfloor}) \\=g(1)S(n) + \sum_{i = 2} ^ ng(i)S({\lfloor {n \over i} \rfloor}) \]

​ 所以:

\[g(1)S(n) = \sum_{i = 1} ^ nh(i) - \sum_{i = 2} ^ ng(i)S({\lfloor {n \over i} \rfloor}) \]

​ 上面的式子就是杜教筛公式。

三、例子

​ 莫比乌斯函数的前缀和, 记为 $ S(n) = \sum_{i = 1} ^n \mu(i) $

​ 因为 \(\sum_{d | m} \mu(d) = [m = 1]\)

​ 所以令 $ f = \mu, h = [m=1], g = 1 $, 则

\[S(n) = 1 - \sum_{i = 2} ^ nS({\lfloor {n \over i} \rfloor}) \]

代码:

#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$, 则

\[S(n) = \sum_{i = 1}^ni - \sum_{i = 2} ^ nS({\lfloor {n \over i} \rfloor}) \]

代码:

#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\) 即可

\[S(n) = \sum_{i = 1} ^ n(i - 2)*(i - 1) - \sum_{i = 2} ^ nS({\lfloor {n \over i} \rfloor}) \]

这题你通过题目中给定的式子可以发现 \(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;
} 
posted @ 2022-04-04 10:30  seekerHeron  阅读(49)  评论(0)    收藏  举报