洛谷P4213 【模板】杜教筛 题解
题目链接:P4213 【模板】杜教筛
题意:
给定一个正整数,求
输入格式:
本题有多组数据。
输入的第一行为一个整数,表示数据组数 $T$。
接下来 $T$ 行,每行一个整数 $n$,表示一组询问。
输出格式:
对于每组询问,输出一行两个整数,分别代表 $\mathrm{ans}_1$ 和 $\mathrm{ans}_2$。
数据范围:
对于全部的测试点,保证 $1 \leq T \leq 10$,$1 \leq n \lt 2^{31}$。
杜教筛是一种能在非线性时间内求解积性函数的前缀和的算法。
设 $S(n) = \sum_{i=1}^{n} f(i)$ ,其中 $f$ 为积性函数。另设积性函数 $g$ ,考虑他们的狄利克雷卷积的前缀和
考察 $g(1)S(n)$ ,可知
由前面的式子可得
于是只要找到一个合适的积性函数 $g$ ,就可以快速计算 $\sum_{i=1}^n(f * g)(i)$ 和 $g$ 的前缀和
这样就可以用数论分块递归地求解(递归 $S$ )。根据狄利克雷卷积的性质,可知
于是求 $\varphi$ 的前缀和可以设 $f=\varphi,\,g = \mathbf{1}$ ,则 $f * g = \mathrm{id}$ ,其前缀和为 $\frac{n(n+1)}{2}$ 。
同理对于 $\mu$ 的前缀和,可设 $f=\mu,\,g=\mathbf{1}$ ,则 $f*g = \epsilon$ ,其前缀和为 $1$ 。
那么杜教筛的基本流程如下
ll GetSum(int n) // 算 f 前缀和的函数
{
ll ans = f_g_sum(n); // 算 f * g 的前缀和
for(ll l = 2, r; l <= n; l = r + 1) // 注意从 2 开始数论分块
{
r = (n / (n / l));
ans -= (g_sum(r) - g_sum(l - 1)) * GetSum(n / l); // g_sum 是 g 的前缀和
} return ans;
}
这个代码的复杂度是 $\mathcal{O}(n^{\frac{3}{4}})$ 的,并非最优方案,不过我们先来证明一下。
证明 设复杂度为 $T(n)$ ,要求出 $S(n)$ 需要求出 $\sqrt{n}$ 个 $S\left(\left\lfloor\frac{n}{i}\right\rfloor\right)$ 的值,那么
$\square$
上面代码的形式使得我们考虑记忆化,并先用线性筛求出前 $m$ 个答案,则
当 $m = n^{\frac{2}{3}}$ 时,$T(n)$ 可以取到 $\mathcal{O}(n^{\frac{2}{3}})$ 。并且显然 $m$ 越大跑的越快。
另外可以想到用哈希表来记忆化,注意如果 $x \le m$ 时就不要扔到哈希表里了,直接查数组即可。
时间复杂度 $\mathcal{O}(Tn^{\frac{2}{3}})$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(5e6 + 15))
char ck[N]; int pcnt, p[N]; int phi[N], mu[N];
unordered_map<int, int> sum_phi, sum_mu;
void init(const int _ = N - 5)
{
phi[1] = mu[1] = 1;
rep(i, 2, _)
{
if(!ck[i]) { p[++pcnt] = i; phi[i] = i - 1; mu[i] = -1; }
for(int j = 1; j <= pcnt && i * p[j] <= _; j++)
{
const int pos = i * p[j]; ck[pos] = true;
if(i % p[j] == 0) { phi[pos] = phi[i] * p[j]; break; }
else { phi[pos] = phi[i] * phi[p[j]]; mu[pos] = -mu[i]; }
}
}
rep(i, 1, _) { phi[i] += phi[i - 1]; mu[i] += mu[i - 1]; }
}
int get_sum_phi(int n)
{
if(n <= N - 5) return phi[n];
if(sum_phi[n]) return sum_phi[n];
int res = 1ll * n * (n + 1) / 2;
for(int l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= 1ll * (r - l + 1) * get_sum_phi(n / l);
}
return sum_phi[n] = res;
}
int get_sum_mu(int n)
{
if(n <= N - 5) return mu[n];
if(sum_mu[n]) return sum_mu[n];
int res = 1ll;
for(int l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= 1ll * (r - l + 1) * get_sum_mu(n / l);
}
return sum_mu[n] = res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int qwq; cin >> qwq; init();
while(qwq--) {
static int x; cin >> x;
cout << get_sum_phi(x) << ' ' << get_sum_mu(x) << '\n';
}
return 0;
}
参考文献: