学习笔记:杜教筛

杜教筛

引入

  1. \(\Phi(n) = \sum\limits_{i = 1}^{n} \phi(n)\)

    对于欧拉函数,有 \(\phi * I = id\),即 \(\phi(n) = n - \sum\limits_{d \mid n, d < n}\phi(n)\)

    所以

    \[\begin{aligned} \sum_{i = 1}^n\phi(i) &= \sum_{i = 1}^n (i - \sum_{d \mid i, d < i}\phi(d))\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\sum_{d \mid i, d < i}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{\frac{i}{d} = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{\lfloor\frac{i}{d}\rfloor}\rfloor}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{i}\rfloor}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\Phi(\lfloor\frac{n}{i}\rfloor)\\ \end{aligned} \]

    于是只需预处理 \(O(\sqrt{n})\)\(\Phi(\lfloor\dfrac{n}{i}\rfloor)\),就可以算出 \(\Phi(n)\)

    第三第四步将枚举因数化为枚举倍数,是常见减小复杂度的做法。

  2. \(M(n) = \sum\limits_{i = 1}^{n} \mu(n)\)

    和欧拉函数类似,利用 \(\mu(n) = [n = 1] - \sum\limits_{d \mid n, d < n}\mu(d)\)

    \[\begin{aligned} \sum_{i = 1}^n\mu(n) &= \sum_{i = 1}^n [i = 1] - \sum\limits_{d \mid i, d < i}\mu(d)\\ &= 1 - \sum_{i = 2}^n\sum_{d \mid i, d < i}\mu(d)\\ &= 1 - \sum_{i = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{i}\rfloor}\mu(d)\\ &= 1 - \sum_{i = 2}^nM(\lfloor\frac{n}{i}\rfloor)\\ \end{aligned} \]

推导

对于一个数论函数 \(f(n)\),杜教筛可以在低于线性复杂度内求 \(S(n) = \sum\limits_{i = 1}^nf(i)\).

如果利用整除分块把相等的一起算,就加快了速度。

根据 \(f(n)\) 的性质,构造 \(S(n)\) 关于 \(S(\lfloor\dfrac{n}{i}\rfloor)\) 的递推式,方法如下。

构造两个积性函数 \(h, \ g\),满足 \(h\) 易于求和,\(g\) 易于计算 且\(h = f * g\)

\(h(i) = \sum\limits_{d \mid i}g(d)\cdot f(\dfrac{i}{d})\),对 \(h(i)\) 求和,有

\[\begin{aligned} \sum_{i = 1}^nh(i) &= \sum_{i = 1}^n\sum\limits_{d \mid i}^ig(d)\cdot f(\dfrac{i}{d})\\ &= \sum_{d = 1}^ng(d)\sum\limits_{d \mid i}^n\cdot f(\dfrac{i}{d})\\ &= \sum_{d = 1}^ng(d)\sum\limits_{i = 1}^{\lfloor\dfrac{n}{d}\rfloor}\cdot f(i)\\ &= \sum_{d = 1}^ng(d)\cdot S(\lfloor\dfrac{n}{d}\rfloor) \end{aligned} \]

  1. 公式法求 \(\sum_{i = 1}^n\phi(n)\)

    \(h = id, \ f = \phi, \ g = I\)

    所以 \(\sum\limits_{i = 1}^n id(i) = \sum\limits_{i = 1}^n S(\lfloor\dfrac{n}{d}\rfloor)\),即 \(S(n) = \sum\limits_{i = 1}^n id(i) - \sum\limits_{i = 2}^n S(\lfloor\dfrac{n}{d}\rfloor)\)

    同理,可用 \(\epsilon = \mu * I\) 求出 \(M(n)\)

实现

对于 \(n < N^{\frac{2}{3}}\) 预处理,否则暴力递归然后记忆化。

复杂度证明:[Auferstanden].

#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;

using uint = unsigned int;
using ll = long long;
constexpr int N = 3e6 + 5, V = 3e6;

__gnu_pbds::gp_hash_table<int, int> Mu;
__gnu_pbds::gp_hash_table<int, ll> Phi;

int mu[N], p[N], v[N], idx;
ll phi[N];

void init() {
	phi[1] = mu[1] = 1;
	
	for(int i = 2; i <= V; ++ i) {
		if(v[i] == 0) {
			mu[i] = -1;
			phi[i] = i - 1;
			p[++ idx] = i;
		}
		for(int j = 1; j <= idx && p[j] <= V / i; ++ j) {
			v[i * p[j]] = 1;			
			if(i % p[j] == 0) {
				phi[i * p[j]] = phi[i] * p[j];
				mu[i * p[j]] = 0;
				break;
			}
			mu[i * p[j]] = -mu[i];
			phi[i * p[j]] = phi[i] * (p[j] - 1);
		}
	}
	
	for(int i = 1; i <= V; ++ i) {
		mu[i] += mu[i - 1];
		phi[i] += phi[i - 1];
	} 
}

ll get_Phi(uint n) {
	if(n <= V) return phi[n];
	if(Phi.find(n) != Phi.end()) return Phi[n];
	ll ans = (ll)n * (n + 1) / 2;
	for(uint i = 2, j; i <= n; i = j + 1) {
		if(n / i == 0) break;
		j = n / (n / i);
		ans -= get_Phi(n / i) * (j - i + 1);
	}
	return Phi[n] = ans;
}
int get_Mu(uint n) {
	if(n <= V) return mu[n];
	if(Mu.find(n) != Mu.end()) return Mu[n];
	int ans = 1;
	for(uint i = 2, j; i <= n; i = j + 1) {
		if(n / i == 0) break;
		j = n / (n / i);
		ans -= get_Mu(n / i) * (j - i + 1);
	}
	return Mu[n] = ans;
}

int main() {
	cin.tie(0)->sync_with_stdio(0);
	
	init();
	
	int T;
	cin >> T;
	
	while(T --) {
		uint n; cin >> n;
		cout << get_Phi(n) << ' ' << get_Mu(n) << '\n';
	}
	return 0;
}

posted @ 2024-04-30 16:57  Lu_xZ  阅读(4)  评论(0编辑  收藏  举报