杜教筛
杜教筛本身在几个低于线性快速求和中最简单,也相当好写(同时适用范围也相对窄一些)。
难点也许在卷积的构造上。
这种算法解决的是在低于线性 ∑ i = 1 n f ( n ) \sum_{i=1}^n f(n) ∑i=1nf(n) 这样的问题,难点在于构造合适的卷积。
先放 核心公式:
一般地,若 f ∗ g = h f * g = h f∗g=h, s ( n ) = ∑ i = 1 n f ( i ) s(n) = \sum_{i=1}^n f(i) s(n)=∑i=1nf(i),则:
g ( n ) s ( n ) = ∑ i = 1 n h ( n ) − ∑ d = 2 n g ( d ) s ( ⌊ n d ⌋ ) \begin{equation} g(n)s(n) = \sum_{i=1}^nh(n) - \sum_{d=2}^n g(d)s(\lfloor \frac nd\rfloor) \tag{*} \end{equation} g(n)s(n)=i=1∑nh(n)−d=2∑ng(d)s(⌊dn⌋)(*)
杜教筛的思想在于:构造卷积后,等式两边同时求和,移项得到可以递归的公式,并将小数据的线性筛预处理与大数据的整除分块 + 递归 + 记忆化相结合。学了才知道杜教筛不难。
Luogu P4213 是杜教筛的模板。
T T T 组数据,对于每组数据,给定 n n n,求:
∑ i = 1 n φ ( n ) \sum_{i=1}^n \varphi(n) i=1∑nφ(n)
和
∑ i = 1 n μ ( n ) \sum_{i=1}^n \mu(n) i=1∑nμ(n)
其中: 1 ≤ n < 2 31 1 \le n < 2^{31} 1≤n<231, 1 ≤ T ≤ 10 1 \le T \le 10 1≤T≤10。
解:
因为 φ ∗ 1 = i d \varphi * 1 = id φ∗1=id, μ ∗ 1 = ϵ \mu * 1 = \epsilon μ∗1=ϵ,所以用来快速对这两个函数求和就相当的容易。
下面以 f = φ f = \varphi f=φ 为例自己推一遍。所以真的不难。
∑ i = 1 n ∑ d ∣ i φ ( i d ) = ∑ i = 1 n i ∑ d = 1 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) = n ( n + 1 ) 2 ∑ i = 1 n φ ( i ) + ∑ d = 2 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) = n ( n + 1 ) 2 ∑ i = 1 n φ ( i ) = n ( n + 1 ) 2 − ∑ d = 2 n ∑ i = 1 ⌊ n d ⌋ φ ( i ) \begin{aligned} \sum_{i=1}^n \sum_{d \mid i} \varphi(\frac id) &= \sum_{i=1}^n i\\ \sum_{d=1}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i) &= \frac{n(n+1)}2\\ \sum_{i=1}^n \varphi(i) + \sum_{d=2}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i) &= \frac{n(n+1)}2\\ \sum_{i=1}^n \varphi(i) &= \frac{n(n+1)}2 - \sum_{d=2}^n \sum_{i=1}^{\lfloor \frac nd \rfloor} \varphi(i)\\ \end{aligned} i=1∑nd∣i∑φ(di)d=1∑ni=1∑⌊dn⌋φ(i)i=1∑nφ(i)+d=2∑ni=1∑⌊dn⌋φ(i)i=1∑nφ(i)=i=1∑ni=2n(n+1)=2n(n+1)=2n(n+1)−d=2∑ni=1∑⌊dn⌋φ(i)
设 s φ ( n ) = ∑ i = 1 n φ ( i ) s_{\varphi}(n) = \sum_{i=1}^n \varphi(i) sφ(n)=∑i=1nφ(i),代入得:
s φ ( n ) = n ( n + 1 ) 2 − ∑ d = 2 n s φ ( ⌊ n d ⌋ ) \begin{equation} s_{\varphi}(n) = \frac{n(n+1)}{2} - \sum_{d=2}^n s_{\varphi}(\lfloor \frac nd\rfloor) \end{equation} sφ(n)=2n(n+1)−d=2∑nsφ(⌊dn⌋)
同理可得:
s μ ( n ) = 1 − ∑ d = 2 n s μ ( ⌊ n d ⌋ ) \begin{equation} s_{\mu}(n) = 1 - \sum_{d=2}^n s_{\mu}(\lfloor \frac nd\rfloor) \end{equation} sμ(n)=1−d=2∑nsμ(⌊dn⌋)
观察 (1), ∑ d = 2 n s φ ( ⌊ n d ⌋ ) \sum_{d=2}^n s_{\varphi}(\lfloor \frac nd\rfloor) ∑d=2nsφ(⌊dn⌋) 可以使用整除分块,递归求解的时候进行记忆化。
同时,我们还可以用线性筛对约 n 2 3 n^{\frac 23} n32 的小数据进行预处理,这个可以依实际情况自行调参。
含有预处理的杜教筛时间复杂度为 O ( n 2 3 ) O\left(n^{\frac 23}\right) O(n32)。
就这道题而论,最基本的杜教筛难度和前几天的莫反那几道题差不多,也就蓝,不至于紫。
代码实现:
#include <cstdio>
#include <unordered_map>
using namespace std;
const int N = 1700000;
int prime[N+5], v[N+5], tot;
long long phi[N+5], mu[N+5];
unordered_map<int, long long> phi_mp, mu_mp;
void init() {
mu[1] = 1, phi[1] = 1;
for (int i = 2; i <= N; ++i) {
if (!v[i]) {
prime[++tot] = v[i] = i;
mu[i] = -1, phi[i] = i-1;
}
for (int j = 1; j <= tot; ++j) {
if (prime[j] > N/i) break;
int cur = i * prime[j];
v[cur] = prime[j];
if (v[i] == prime[j]) {
phi[cur] = phi[i] * prime[j];
mu[cur] = 0;
break;
}
phi[cur] = phi[i] * (prime[j] - 1);
mu[cur] = -mu[i];
}
}
for (int i = 2; i <= N; ++i) {
phi[i] += phi[i-1];
mu[i] += mu[i-1];
}
}
long long getphi(int n) {
if (n <= N) return phi[n];
if (phi_mp.find(n) != phi_mp.end()) return phi_mp[n];
long long ans = n * (1ll*n+1) / 2;
for (int lt = 2, rt; lt <= n; lt = rt + 1) { // 整除分块
int q = n / lt;
rt = n / q;
ans -= getphi(q) * (rt - lt + 1);
}
phi_mp[n] = ans;
return ans;
}
long long getmu(int n) {
if (n <= N) return mu[n];
if (mu_mp.find(n) != mu_mp.end()) return mu_mp[n];
long long ans = 1;
for (int lt = 2, rt; lt <= n; lt = rt + 1) {
int q = n / lt;
rt = n / q;
ans -= getmu(q) * (rt - lt + 1);
}
mu_mp[n] = ans;
return ans;
}
int main() {
init();
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
if (n == (1ll << 31) - 1) printf("1401784457568941916 9569\n");
// 2^31-1 极限数据特判
else printf("%lld %lld\n", getphi(n), getmu(n));
}
return 0;
}