杜教筛

1 积性函数和狄利克雷卷积

1.1 积性函数

1.1.1 定义

积性函数在以前的学习中遇到过很多,例如莫比乌斯函数 μ(n),欧拉函数 φ(n) 等等。那么我们对积性函数定义如下:

  • 称定义域在正整数上的函数叫做数论函数(也叫算数函数)。
  • 对于一个数论函数 f(n),如果 f(xy)=f(x)f(y) 对于任意互质的 x,yN+ 都成立,则称 f(n) 为积性函数。
  • 特别的,对于一个数论函数 f(n),如果 f(xy)=f(x)f(y) 对于任意的 x,yN+ 都成立,则称 f(n) 为完全积性函数。

1.1.2 常见积性函数

  • 单位函数:ε(n)=[n=1](完全积性)。
  • 恒等函数:idk(n)=nk(完全积性)。其中 id1(n)=n,通常简记为 id(n)
  • 常数函数:1(n)=1(完全积性)。
  • 除数函数:σk(n)=dndk。其中 σ0(n) 即因子个数,通常简记为 d(n)σ1(n) 即因子之和,通常简记为 σ(n)
  • 欧拉函数:φ(n)=i=1n[gcd(i,n)=1]
  • 莫比乌斯函数:μ(n)

1.2 狄利克雷卷积

1.2.1 定义

对于两个数论函数 f(x)g(x),它们的狄利克雷卷积得到的结果 h(x) 定义为:

h(x)=dxf(d)g(xd)

上式通常简记为:

h=fg

例如在 1.1.2 中出现的一些函数,我们会有如下式子:

  • ε=μ1
  • d=11
  • σ=id1
  • φ=μid

1.2.2 性质

  • 交换律:fg=gf
  • 结合律:(fg)h=f(gh)
  • 分配率:(f+g)h=fh+gh

2 杜教筛

2.1 引入

首先需要了解的前置知识:积性函数、狄利克雷卷积、数论分块。

杜教筛处理的是对于一类数论函数 f(n),求其前缀和 S(n)=i=1nf(i) 的值。通常情况下 n 会很大,所以线性复杂度是不可行的。我们考虑数论分块,假如我们能够构造一个 S(n) 关于 S(ni) 的递推式,就可以用数论分块快速求解了。

2.2 算法思想

对于一个数论函数 g,会有如下等式:

i=1n(fg)(i)=i=1ndig(d)f(id)=i=1nj=1nig(i)f(j)=i=1ng(i)j=1nif(j)=i=1ng(i)S(ni)

于是会有如下递推式:

g(1)S(n)=i=1ng(i)S(ni)i=2ng(i)S(ni)=i=1n(fg)(i)i=2ng(i)S(ni)

假如我们可以构造适当的函数 g,使得:

  • 可以快速求解出 i=1n(fg)(i)
  • 可以快速求解出 g(i) 的前缀和,以便后面使用数论分块求解 i=2ng(i)S(ni)

我们就能在较短时间内求得 g(1)S(n)

具体来讲,杜教筛的时间复杂度是 O(n3/4) 的。如果我们可以快速求出前 n2/3 项的函数值(例如使用线性筛),就可以降低复杂度至 O(n2/3)。值得注意的是,这个时间复杂度是基于记忆化搜索的,因此需要使用 map 等数据结构存储下 S(i) 的值。

2.3 例题

例 1:【模板】杜教筛

题意:i=1nφ(i)i=1nμ(i)

先考虑求 S1(n)=i=1nμ(i)

根据狄利克雷卷积可知,ε=μ1,所以直接构造 g(n)=1(n) 即可,于是:

S1(n)=i=1nμ(i)=i=1nε(i)i=2n1(i)S1(ni)=1i=2nS1(ni)

然后考虑求 S2(n)=i=1nφ(i),实际上这个用莫比乌斯反演更容易得出,最后可以转化为求 μ 的前缀和。不过我们同样可以使用杜教筛求解。

关于欧拉函数有一个广为人知的性质:dnφ(d)=n,即 φ1=id。通过这个也就可以证明出上文提到的 φ=μid 的结论了。

我们仍然构造 g(n)=1(n),于是:

S2(n)=i=1nφ(i)=i=1nid(i)i=2n1(i)S2(ni)=n(n+1)2i=2nS2(ni)

代码如下:

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#define int long long

using namespace __gnu_pbds;
using namespace std;

const int Maxn = 2e6 + 5;
const int Inf = 2e9;

int T;
int n;

int prim[Maxn], cnt, mu[Maxn], phi[Maxn];
bool vis[Maxn];

const int N = 2e6;
void init() {
	mu[1] = 1;
	phi[1] = 1;
	for(int i = 2; i <= N; i++) {
		if(!vis[i]) {
			prim[++cnt] = i;
			mu[i] = -1;
			phi[i] = i - 1;
		}
		for(int j = 1, x; j <= cnt && (x = prim[j] * i) <= N; j++) {
			vis[x] = 1;
			if(i % prim[j] == 0) {
				mu[x] = 0;
				phi[x] = phi[i] * prim[j];
				break;
			}
			mu[x] = -mu[i];
			phi[x] = phi[i] * (prim[j] - 1);
		}
	}
	for(int i = 1; i <= N; i++) {
		mu[i] += mu[i - 1];
		phi[i] += phi[i - 1];
	}
}

gp_hash_table <int, int> smu, sphi;

int sum_mu(int x) {
	if(x <= N) return mu[x];
	if(smu[x]) return smu[x];
	int l = 2, r = 0, res = 1;
	while(l <= x) {
		r = x / (x / l);
		res -= sum_mu(x / l) * (r - l + 1);
		l = r + 1;
	}
	return smu[x] = res;
}

int sum_phi(int x) {
	if(x <= N) return phi[x];
	if(sphi[x]) return sphi[x];
	int l = 2, r = 0, res = (x + 1) * x / 2;
	while(l <= x) {
		r = x / (x / l);
		res -= sum_phi(x / l) * (r - l + 1);
		l = r + 1;
	}
	return sphi[x] = res;
}

void solve() {
	cin >> n;
	cout << sum_phi(n) << " " << sum_mu(n) << '\n';
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> T;
	init();
	while(T--) solve();
	return 0;
}

例 2:[CQOI2015] 选数

题意: 求出在值域区间 [L,H] 中选出 n 个数且它们的 gcd 恰为 k 的方案数。

这是经典 gcd 容斥了,设 f(x) 表示选出的 n 个数的 gcdx 的倍数的方案数,g(x) 表示选出的 n 个数的 gcd 恰为 x 的方案数。容易得到:

f(x)=xdg(d)

根据莫比乌斯反演可得:

g(x)=xdμ(dx)f(d)

根据定义,我们又知道 f(x)=(RxLx+1)n=(RxL1x)n,所以我们就可以轻松得出:

g(x)=xdμ(dx)(RdL1d)n

但是发现这个式子无法通过数论分块去优化复杂度,因此需要另辟蹊径。考虑在原始的 g(x) 式子中不去枚举 d,转而去枚举 dx,可以得到:

g(x)=i=1Rkμ(i)f(ki)=i=1Rkμ(i)(RkiL1ki)n

R=Rk,L=L1k,则:

g(x)=i=1Rμ(i)(RiLi)n

此时整个式子就可以数论分块求解了,但是此时发现线性求 μ 的前缀和复杂度仍然会炸,所以使用杜教筛求其前缀和即可。

例 3: [BZOJ3512] DZY Loves Math IV

题意:i=1nj=1mφ(ij)

观察到一个很重要的点,n105,这启示我们通过枚举 n 来求解。

考虑设 S(n,m)=i=1mφ(ni)。这个时候我们的目标是将 φ 里面乘积的形式拆开,不然很难做。一个初步的想法是,提取出他们都有的公共质因子,将其中一个数的所有这些质因子全部除掉,这样两个数就互质了。不过这个形式难以表达,不妨再考虑一点,即对欧拉函数产生贡献的实际上是不同的质因子,也就是说每个质因子只需要保留一个,剩下的直接乘上去即可。

此时我们考虑标准分解式,令 n=piai,设 x=pi,y=nx,这两者均可以欧拉筛简单求出。则 φ(ni)=φ(xyi)=y×φ(xi)。此时我们对后面的 φ 运用刚刚推出来的结论,发现此时他们的公共质因子就是 gcd(x,i),并且让 x 除掉所有这些质因子其实就是 xgcd(x,i)。所以我们就可以做如下变换:

S(n,m)=i=1mφ(ni)=yi=1mφ(xi)=yi=1mφ(xgcd(x,i))φ(i)gcd(x,i)=yi=1mφ(xgcd(x,i))φ(i)dgcd(x,i)φ(d)=yi=1mφ(i)dgcd(x,i)φ(d)φ(xgcd(x,i))

不难发现此时 dxgcd(x,i) 一定互质,所以可以直接相乘。可以得到:φ(dxgcd(x,i))=φ(xgcd(x,i)d)。考虑到 gcd(x,i)d 其实就是 gcd(x,i) 的因数,所以我们改为枚举这个值,可以得到:

S(n,m)=yi=1mφ(i)dgcd(x,i)φ(xd)=ydxφ(xd)dimφ(i)=ydxφ(xd)i=1mdφ(di)=ydxφ(xd)S(d,md)

发现这个式子是一个递归的形式,我们采用类似杜教筛的形式求解这个式子即可。当然还注意递归边界,当 n=1 时返回的是 i=1mφ(i),这个直接利用杜教筛求解即可;当 m=0m=1 时返回对应值即可。

考虑一下它的时间复杂度,在 n=1 的时候我们会计算一边 φ 的前缀和,并且每一个 m 只算一次,实际上是 O(m2/3) 的。再考虑递归复杂度,发现后一项根据数论分块易得最多 2m 种取值,而前一项由于每一次的 x 中每个质因子只有一个,所以其总的因子数量不会很多,因此可以通过。

posted @   UKE_Automation  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示