P4213 学习笔记 & 杜教筛详解

杜教筛

我们之前通过线性筛(欧拉筛)求出了 \(\mu(i)\)\(\phi(i)\) 的前缀和,以优秀的 \(O(n)\) 抢占先机。

但是,我们在 \(O(nq)\) 的时间复杂度下百万量级的数据是过不了的。

你不要跟我说 WC2026 的文艺汇演说

我们的每一档部分分都是意料之外的
暴力的,\(O(nq)\) 是可过的

那个纯属节目效果。

杜教筛可以优化为 \(O(n^{\frac 23})\),你可能觉得差别微小,但是人家 \(n \le 2^{31}-1\) 差别就大了。

设我们现在需要求前缀和的积性函数为 \(f(x)\),则我们现在是要以优秀的时间复杂度算出

\[S(n)=\sum_{i=1}^n f(i) \]

我们考虑再构造两个积性函数 \(h\)\(g\),满足卷积

\[h=g \cdot f \]

\[h(i)=\sum_{d \mid i} g(d)f(\frac id) \]

对其求和,有

\[\begin{align*} \sum_{i=1}^n h(i)&=\sum_{i=1}^n \sum_{d \mid i} g(d) f(\frac id)\\ &=\sum_{d=1}^n g(d) \sum_{d \mid i} f(\frac id)\\ &=\sum_{d=1}^n g(d) \sum_{i=1}^{\lfloor \frac nd \rfloor} f(i)\\ &=\sum_{d=1}^n g(d) S(\lfloor \frac nd \rfloor)\\ \end{align*} \]

(称上述推导中第一行到第二行为杜教筛变换)

而我们需要计算 \(S(n)\),于是考虑把 \(d=1\) 的项提出来,变成

\[\sum_{i=1}^n h(i)=g(1)S(n)+\sum_{d=2}^n g(d) S(\lfloor \frac nd \rfloor) \]

于是

\[g(1)S(n)=\sum_{i=1}^n h(i)-\sum_{d=2}^n g(d) S(\lfloor \frac nd \rfloor) \]

即杜教筛公式。

公式中形似整除分块的东西用整除分块算就行了。

P4213

我们需要求积性函数 \(\phi(i)\)\(\mu(i)\) 的前缀和。

\(\phi(i)\)

先套公式,我们需要定义卷积

\[h=g \cdot f=\sum_{d \mid n} f(d) g(\frac nd) \]

\[n=\sum_{d \mid n} \phi(d) \]

改写为

\[id=\phi \cdot I \]

从而

\[h=id,g=I \]

代入公式

\[g(1)S(n)=\sum_{i=1}^n h(i)-\sum_{i=2}^n g(i) S(\lfloor \frac ni \rfloor) \]

\[S(n)=\sum_{i=1}^n i-\sum_{i=2}^n S(\lfloor \frac ni \rfloor)=\frac 12 n(n+1)-\sum_{i=2}^n S(\lfloor \frac ni \rfloor) \]

整除分块。

\(\mu(i)\)

首先,\(\mu(i)\) 有性质

\[\sum_{d \mid n} \mu(d)=\begin{cases} 1 & n=1\\ 0 & n>1 \end{cases} \]

为了方便,我们记为

\[\sum_{d \mid n} \mu(d)=[n=1] \]

套用公式,构造卷积

\[h=f \cdot g=\sum_{d \mid n} f(d) g(\frac nd) \]

\[\sum_{d \mid n} \mu(d)=[n=1] \]

改写为

\[\varepsilon =\mu \cdot I \]

\[h=\varepsilon,g=I \]

代入

\[g(1)S(n)=\sum_{i=1}^n h(i)-\sum_{i=2}^n g(d)S(\lfloor \frac ni \rfloor) \]

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

code
#include <bits/stdc++.h>
#define pub public:
#define pri private:
#define fri friend:
#define Ofile(s) freopen(s".in", "r", stdin), freopen (s".out", "w", stdout)
#define Cfile(s) fclose(stdin), fclose(stdout)
#define fast ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;

using ll = long long;
using ull = unsigned long long;
using lb = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using pil = pair<int, ll>;
using pli = pair<ll, int>;

constexpr int mod = 998244353;
constexpr int maxn = 5e6 + 5;

ll t;
ll n, tot;

ll prime[maxn], mu[maxn], phi[maxn];
bool vis[maxn];

unordered_map <ll, ll> sum_mu, sum_phi; // 用来记忆化的两个东西

void Du_sieve(); // 杜教筛
ll getsmu(ll x); // mu(i) 的前缀和
ll getsphi(ll x); // phi(i) 的前缀和

int main() {
	freopen("std.in", "r", stdin);
	freopen("std.out", "w", stdout);
	fast;
	Du_sieve();
	cin >> t;
	while (t--){
		cin >> n;
		cout << getsphi(n) << ' ' << getsmu(n) << endl;
	}
	return 0;
}

void Du_sieve(){
	vis[0] = vis[1] = 1;
	mu[1] = phi[1] = 1;
	for (ll i = 2; i <= maxn - 5; i++){
		if (!vis[i]){
			prime[++tot] = i;
			mu[i] = -1;
			phi[i] = i - 1;
		}
		for (ll j = 1; j <= tot && i * prime[j] <= maxn - 5; j++){
			vis[i * prime[j]] = true;
			if (i % prime[j]){
				mu[i * prime[j]] = -mu[i];
				phi[i * prime[j]] = phi[i] * phi[prime[j]];
			}
			else {
				mu[i * prime[j]] = 0;
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			
		}
	} // 线性筛先预处理一部分
	for (ll i = 1; i <= maxn - 5; i++)
		mu[i] += mu[i - 1], phi[i] += phi[i - 1]; // 把两个数组变成存储前缀和
}

ll getsmu(ll x){
	if (x <= maxn - 5)
		return mu[x]; // 有直接用的就直接用
	if (sum_mu[x])
		return sum_mu[x]; // 记忆化,只算一次
	ll res = 1; // 刚刚推导出的1
	for (ll pl = 2, pr; pl <= x; pl = pr + 1){ // 整除分块
		pr = x / (x / pl);
		res -= (pr - pl + 1) * getsmu(x / pl);
	}
	return sum_mu[x] = res; // 记得修改
}

ll getsphi(ll x){
	if (x <= maxn - 5)
		return phi[x]; // 有直接用的就直接用
	if (sum_phi[x])
		return sum_phi[x]; // 记忆化,只算一次
	ll res = x * (x + 1) / 2; // 公式中 1~x 的和
	for (ll pl = 2, pr; pl <= x; pl = pr + 1){ // 整除分块
		pr = x / (x / pl);
		res -= (pr - pl + 1) * getsphi(x / pl);
	}
	return sum_phi[x] = res; // 记得修改
}
posted @ 2026-03-18 13:30  constexpr_ll  阅读(9)  评论(0)    收藏  举报