杜教筛 学习笔记

杜教筛 学习笔记

前置知识:数论函数

(这前置知识分量真大)

给几篇博客:

OI Wiki-莫比乌斯反演

铃悬的数学小讲堂——狄利克雷卷积与莫比乌斯反演

Reference

我的学习博客来自

铃悬的数学小讲堂——杜教筛

杜教筛

我们先理解杜教筛可以干啥。

它可以在亚于线性的时间内求出某个数论函数的前缀和。

本质上是利用了狄利克雷卷积的运算性质。

下面我们来推柿子。

假若我们要求的是 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^nf(i) S(n)=i=1nf(i),我们找到一个辅助函数 g ( n ) g(n) g(n),使 g ( n ) , ( f ∗ g ) ( n ) g(n),(f*g)(n) g(n),(fg)(n) 的前缀和都利于计算。

于是

∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n ∑ d ∣ i f ( ⌊ n d ⌋ ) g ( d ) \sum\limits_{i=1}^n(f*g)(i)=\sum\limits_{i=1}^n\sum\limits_{d|i}f(\lfloor \dfrac nd \rfloor)g(d) i=1n(fg)(i)=i=1ndif(dn)g(d)

= ∑ d = 1 n g ( d ) ∑ i = 1 ⌊ n d ⌋ f ( i ) =\sum\limits_{d=1}^ng(d)\sum\limits_{i=1}^{\lfloor \frac nd \rfloor}f(i) =d=1ng(d)i=1dnf(i)

= ∑ d = 1 n g ( d ) S ( ⌊ n d ⌋ ) =\sum\limits_{d=1}^ng(d)S(\lfloor \dfrac nd\rfloor) =d=1ng(d)S(dn)

我们分出个 d = 1 d=1 d=1 的情况出来。

= g ( 1 ) S ( n ) + ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) =g(1)S(n)+\sum\limits_{d=2}^ng(d)S(\lfloor \dfrac nd\rfloor) =g(1)S(n)+d=2ng(d)S(dn)

于是得到了一个递归计算柿:

S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) g ( 1 ) S(n)=\dfrac{\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{d=2}^ng(d)S(\lfloor \dfrac nd \rfloor)}{g(1)} S(n)=g(1)i=1n(fg)(i)d=2ng(d)S(dn)

前面的那个 f ∗ g f*g fg 的前缀和要 快速计算,后面由于使用整除分块, g g g 的前缀和也要 快速计算。(关于什么叫做 快速计算,请见下文)。

这个柿子是杜教筛的核心。而核心的核心就是 g g g 的寻找。

在此不进行复杂度证明,仅给出相关结论(详细证明请看上面博客):

裸的杜教筛(加记忆化)的复杂度为 O ( n 3 / 4 ) \mathcal O(n^{3/4}) O(n3/4)

如果线性筛预处理所有的 x ≤ n 2 / 3 x\le n^{2/3} xn2/3 S ( x ) S(x) S(x) 的值,递归到 x ≤ n 2 / 3 x\le n^{2/3} xn2/3 时直接返回,那么时间复杂度为 O ( n 2 / 3 ) \mathcal O(n^{2/3}) O(n2/3)

下面解释上文的 快速计算

根据杜教筛的证明,如果 ∑ i = 1 n ( f ∗ g ) ( i ) \sum\limits_{i=1}^n(f*g)(i) i=1n(fg)(i) ∑ i = 1 n g ( i ) \sum\limits_{i=1}^n g(i) i=1ng(i) 可以用 不劣于 杜教筛的算法算出,那么不会影响复杂度,仍为 O ( n 2 / 3 ) \mathcal O(n^{2/3}) O(n2/3)

例题

P4213 【模板】杜教筛(Sum)

μ ( n ) , φ ( n ) \mu(n),\varphi(n) μ(n),φ(n) 的前缀和。

n < 2 31 n< 2^{31} n<231

还记得那个柿子吗。

S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ d = 2 n g ( d ) S ( ⌊ n d ⌋ ) g ( 1 ) S(n)=\dfrac{\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{d=2}^ng(d)S(\lfloor \dfrac nd \rfloor)}{g(1)} S(n)=g(1)i=1n(fg)(i)d=2ng(d)S(dn)

我们先考虑怎么求 μ ( i ) \mu(i) μ(i) 的前缀和。

我们知道 μ ∗ 1 = ϵ \mu * 1=\epsilon μ1=ϵ,于是不妨让 g = 1 g=\mathrm{1} g=1,则 f ∗ g = ϵ f*g=\epsilon fg=ϵ

得到 S ( n ) = 1 − ∑ d = 2 n S ( ⌊ n d ⌋ ) S(n)=1-\sum\limits_{d=2}^nS(\lfloor\dfrac nd\rfloor) S(n)=1d=2nS(dn)

同理,由于 φ ∗ 1 = i d \varphi * 1 =\mathrm{id} φ1=id,我们让 g ′ = 1 g'=1 g=1,则 f ′ ∗ g ′ = i d f'*g'=\mathrm{id} fg=id

得到 S ′ ( n ) = n ( n + 1 ) 2 − ∑ d = 2 n S ′ ( ⌊ n d ⌋ ) S'(n)=\dfrac {n(n+1)}2-\sum\limits_{d=2}^n S'(\lfloor \dfrac nd\rfloor) S(n)=2n(n+1)d=2nS(dn)

可以杜教筛计算了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const ll MAXN = 1e10 + 5, PN = 1.7e6;
int ip[PN+5], tot;
ll mu[PN+5], phi[PN+5], pr[PN+5];
ll n, fmu[MAXN / PN + 10], fphi[MAXN / PN + 10];
int vmu[MAXN / PN + 10], vphi[MAXN / PN + 10];
void init(ll n) {
	ip[1] = 1; mu[1] = phi[1] = 1;
	for(int i = 2; i <= n; i++) {
		if(!ip[i]) {
			pr[++tot] = i;
			mu[i] = -1;
			phi[i] = i-1;
		}
		for(int j = 1; j <= tot && 1ll * i * pr[j] <= n; j++) {
			int k = pr[j] * i;
			ip[k] = 1;
			if(i % pr[j]) {
				mu[k] = -mu[i];
				phi[k] = phi[i] * (pr[j] - 1);
			} else {
				mu[k] = 0;
				phi[k] = phi[i] * pr[j];
				break;
			}
		}
	}
	for(int i = 2; i <= n; i++) phi[i] += phi[i-1], mu[i] += mu[i-1];
}
ll calcphi(ll x) {
	if(x <= PN) return phi[x];
	if(vphi[n / x]) return fphi[n / x];
	vphi[n / x] = 1;
	ll ans = x * (x + 1) / 2;
	for(unsigned i = 2, j; i <= x; i = j+1) {
		j = x / (x/i);
		ans -= (j - i + 1) * calcphi(x/i);
	}
	return fphi[n / x] = ans;
}
ll calcmu(ll x) {
	if(x <= PN) return mu[x];
	if(vmu[n / x]) return fmu[n / x];
	vmu[n / x] = 1;
	ll ans = 1;
	for(unsigned i = 2, j; i <= x; i = j+1) {
		j = x / (x/i);
		ans -= (j - i + 1) * calcmu(x/i);
	}
	return fmu[n / x] = ans;
}
int main() {
	init(PN);
	int T = read();
	for(int i = 1; i <= T; i++) {
		n = read();
		memset(vmu, 0x00, sizeof vmu);
		memset(vphi, 0x00, sizeof vphi);
		printf("%lld %lld\n", calcphi(n), calcmu(n));
	}
	return 0;
}

tips:这里有个记忆化的技巧:我们要存 x > n 2 / 3 x> n^{2/3} x>n2/3 处的计算结果,根据杜教筛的递归特性, ⌊ n x ⌋ \lfloor \dfrac nx\rfloor xn 各不相同,于是我们可以用数组存储所有的 ⌊ n x ⌋ ≤ n 1 / 3 \lfloor \dfrac nx\rfloor\le n^{1/3} xnn1/3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值