杜教筛学习笔记

本文介绍了杜教筛算法,通过狄利克雷卷积作为前置知识,详细讲解了杜教筛的原理,并给出了一些实际例子,如求解前n个正整数的欧拉函数φ(n)的和。此外,还讨论了算法的时间复杂度优化,包括预处理前缀和和采用记忆化技术减少重复计算,最后给出了相关题目及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前置知识

狄利克雷卷积

f ( n ) = ∑ i ∣ n g ( i ) h ( n i ) f(n) = \sum\limits_{i|n}g(i)h(\dfrac{n}{i}) f(n)=ing(i)h(in)
则记 f = g ∗ h f = g*h f=gh

杜教筛

假如要求某积性函数 f ( n ) f(n) f(n) 的前缀和 ( n < 1 0 12 ) (n<10^{12}) (n<1012)
假设另一个积性函数为 g ( n ) g(n) g(n) h = f ∗ g h = f*g h=fg,记 s ( n ) = ∑ i = 1 n f ( i ) s(n) = \sum\limits_{i=1}^{n}f(i) s(n)=i=1nf(i)
则有 ∑ i = 1 n h ( n ) = ∑ i = 1 n ∑ j ∣ i g ( j ) f ( i j ) = ∑ j = 1 n g ( j ) ∑ i = 1 ⌊ n j ⌋ f ( i ) = ∑ j = 1 n g ( j ) s ( ⌊ n j ⌋ ) \sum\limits_{i=1}^{n}h(n) = \sum\limits_{i=1}^{n}\sum\limits_{j|i}g(j)f(\dfrac{i}{j})\\=\sum\limits_{j=1}^{n}g(j)\sum\limits_{i=1}^{{\tiny\left\lfloor\dfrac{n}{j}\right\rfloor}}f(i)=\sum\limits_{j=1}^{n}g(j)s(\left\lfloor\dfrac{n}{j}\right\rfloor) i=1nh(n)=i=1njig(j)f(ji)=j=1ng(j)i=1jnf(i)=j=1ng(j)s(jn)
然后把 j = 1 j = 1 j=1 单独拎出来,有
∑ i = 1 n h ( n ) = g ( 1 ) s ( n ) + ∑ j = 2 n g ( j ) s ( ⌊ n j ⌋ ) \sum\limits_{i=1}^{n}h(n)=g(1)s(n) + \sum\limits_{j=2}^{n}g(j)s(\left\lfloor\dfrac{n}{j}\right\rfloor) i=1nh(n)=g(1)s(n)+j=2ng(j)s(jn)
那么 s ( n ) = ∑ i = 1 n h ( n ) − ∑ j = 2 n g ( j ) s ( ⌊ n j ⌋ ) g ( 1 ) s(n) = \dfrac{\sum\limits_{i=1}^{n}h(n)-\sum\limits_{j=2}^{n}g(j)s(\left\lfloor\dfrac{n}{j}\right\rfloor)}{g(1)} s(n)=g(1)i=1nh(n)j=2ng(j)s(jn)
如果 h ( n ) h(n) h(n) 可以快速求出来,我们就能很快求出 s ( n ) s(n) s(n)

下面举一些例子

∑ i = 1 n φ ( n )   ( n < 1 0 11 ) \sum\limits_{i = 1}^{n}\varphi(n)~(n<10^{11}) i=1nφ(n) (n<1011)
注意到 n = ∑ i ∣ n φ ( i ) n = \sum\limits_{i|n}\varphi(i) n=inφ(i),也就是 I d = φ ∗ 1 Id = \varphi*1 Id=φ1
那么我们让 h ( n ) = n h(n) = n h(n)=n g ( n ) = 1 g(n) = 1 g(n)=1
代入上式中,那就有
s ( n ) = n ∗ ( n + 1 ) 2 − ∑ j = 2 n s ( ⌊ n j ⌋ ) s(n)=\dfrac{n*(n+1)}{2}-\sum\limits_{j=2}^{n}s(\left\lfloor\dfrac{n}{j}\right\rfloor) s(n)=2n(n+1)j=2ns(jn)
然后 n \sqrt{n} n 枚举关键点 j j j,递归求 s ( ⌊ n j ⌋ ) s(\left\lfloor\dfrac{n}{j}\right\rfloor) s(jn)
复杂度大概是 O ( n 3 4 ) O(n^{\tiny\dfrac{3}{4}}) O(n43)
为了加速,我们可以先线性预处理 1 e 7 1e7 1e7 的前缀和
这样递归只用递归大于 1 e 7 1e7 1e7 的关键点
然后对于大于 1 e 7 1e7 1e7 的关键点,为了防止重复访问,我们采用记忆化,将 x x x 这个点的值存在 f [ n / x ] f[n/x] f[n/x] 里面。
复杂度大概是 O ( n 2 3 ) O(n^{\tiny\dfrac{2}{3}}) O(n32)

一道模板题

洛谷 p 4213 p4213 p4213
在这里插入图片描述

代码如下

#include <bits/stdc++.h>
#define LL long long
#define N 3000005
using namespace std;
LL z = 1, sphi[N], t, smu[N], f[5005], g[5005];
int maxn = N - 5, cnt, n, p[N], x[N], mu[N], phi[N];
LL getphi(int x){
	int i, j;
	LL s = 0;
	unsigned l, r;
	if(x <= maxn) return sphi[x];
	if(f[n / x]) return f[n / x];
	for(l = 2; l <= x; l = r + 1){
		r = x / (x / l);
		s = s + (r - l + 1) * getphi(x / l);
	}
	f[n / x] = z * x * (x + 1) / 2 - s;
	return f[n / x];
}
LL getmu(int x){
	int i, j;
	LL s = 0;
	unsigned l, r;
	if(x <= maxn) return smu[x];
	if(g[n / x]) return g[n / x];
	for(l = 2; l <= x; l = r + 1){
		r = x / (x / l);
		s = s + (r - l + 1) * getmu(x / l);
	}
	g[n / x] = z - s;
	return g[n / x];
}
int main(){
	int i, j, T;
	mu[1] = phi[1] = 1;
	for(i = 2; i <= maxn; i++){
		if(!x[i]) x[i] = p[++cnt] = i, mu[i] = -1;
		for(j = 1; j <= cnt; j++){
			t = z * i * p[j];
			if(t > maxn) break;
			x[t] = p[j];
			if(i % p[j] == 0) break;
			mu[t] = -mu[i];
		}
	}
	for(i = 2; i <= maxn; i++){
		if(x[i / x[i]] == x[i]) phi[i] = phi[i / x[i]] * x[i];
		else phi[i] = phi[i / x[i]] * (x[i] - 1);
	}
	for(i = 1; i <= maxn; i++) sphi[i] = sphi[i - 1] + phi[i], smu[i] = smu[i - 1] + mu[i];
	scanf("%d", &T);
	while(T--){
		memset(f, 0, sizeof(f));
		memset(g, 0, sizeof(g));
		scanf("%d", &n);
		printf("%lld %lld\n", getphi(n), getmu(n));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值