Note -「Min_25 筛」“你就说这素因子你要不要吧?你要不要?”
赛上想写,Track Lost 了属于是。
\(\mathscr{Intro}\)
Min_25 筛是用于求积性函数前缀和,同时顺带求出一些“有意思”的信息的筛法。
一些记号约定
- \(\mathbb P\) 为素数集,对于以 \(p\) 为记号的数,有 \(p\in\mathbb P\)。
- \(p_i\) 表示第 \(i\) 小的素数。特别地,\(p_0=1\)。
- \(\newcommand{\lpf}[0]{\operatorname{lpf}} \lpf(n)\) 表示 \(n\) 的最小素因子。
- \(a/b=\lfloor\frac{a}{b}\rfloor\).
\(\mathscr{Algorithm}\)
明确我们的目标:对于积性函数 \(f(n)\),求出
此后,我们用 \(m\) 来表示一般的求和上标,而 \(n\) 恒为问题中待求的前缀值。
Min_25 筛分两步走。第一步:对于所有 \(x\in D=\{n/i\mid i\in[1,n]\}\),求出 \(\sum_{p\le x} f(p)\)。
这里需要运用 Min_25 筛的核心思想:首先承认所有合数为素数,然后逐步筛掉它们,修正得到正确答案。形式地,令 \(f_P(x)\) 表示 \(f(p)\) 处关于 \(p\) 的多项式,当然我们需要把 \(f_P(x)\) 的定义域从 \(\mathbb P\) 强行钦定为 \(\mathbb N^\star\);最后,定义 \(G(m,i)\) 为
它表示对于 \(2\sim m\),只用前 \(i\) 个素数做埃筛后,承认剩下的数都是素数,得到的 \(f\) 之和。可见我们的目标就是求出所有 \(G(x,\pi(\lfloor\sqrt{n}\rfloor))\)。尝试写出 \(G(m,i)\) 的递推式:
如何优化掉和式?为了将和式转化成 \(G\) 的形式,自然的想法是令 \(j\leftarrow j/p_i\),那么此时就必须追加一个条件:\(f_P(x)\) 为完全积性函数。借此进一步转化:
注意 \(j<i\le\pi(\lfloor\sqrt n\rfloor)\),所以 \(P(m)=\sum_{i=1}^mf_P(p_i)\) 可以直接预处理出来。复杂度待会儿说√
可见,就算仅使用 Min_25 筛的第一步,也能解决一些问题。例如令 \(f(n)=1\),就能计算 \(\pi(n)\)。
第二步:对于所有 \(x\in D\),求出 \(\sum_{i=2}^xf(i)\)。
还是利用同样的思想,定义 \(F(m,i)\) 为
它表示素数以及所有被 \(p_{i+1..\pi(n)}\) 筛掉的合数的 \(f\) 之和,\(m\) 的最终答案即为 \(F(m,0)\)。类似与 \(G\) 的转移,不过由于 \(f\) 不一定完全积性,我们不得不枚举 \(p_i\) 的指数,有
注意 \(f(p_i)\) 早已计算过,所以和式的第二项从 \(f(p_i^2)\) 开始累加。
这里还有一种两层和式的递推方式,据说会引出 \(\mathcal O(n^{1-\epsilon})\) 的递归求解算法。我不会证,而且我一写出来就是这种一层和式,你让我怎么硬生生再套一层,所以略过,抱歉 qwq。
对于 \(F,G\) 的状态复杂度,有
为了方便理解,\(\log\) 都确切地写作 \(\ln\) 啦。
这里有个疑点,为什么 \(F\) 的递推形式转移不影响复杂度。没找到资料 qwq。
UPD: 悟了,前常数个 \(j\) 直接放成 \(O\left(\frac{n^{\frac{3}{4}}}{\ln n}\right)\),直到后面的 \(j\) 全部放成 \(\ln\) 倍的某一复杂度都比不过,就能证了。
对于空间复杂度,滚动 \(i\),\(F(m,i)\) 和 \(G(m,i)\) 都只需要记录 \(\mathcal O(\sqrt n)\) 个 \(n/i\) 的点值。
注意:为保证复杂度,不能访问任何非法状态,否则退化成 \(\mathcal O(n^{1-\epsilon})\)。
UPD: 放个实例代码吧方便复习。Problem link.
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
typedef long long LL;
const int MAXSN = 316228;
int pn, pr[MAXSN + 5];
bool npr[MAXSN + 5];
inline void init() {
rep (i, 2, MAXSN) {
if (!npr[i]) pr[++pn] = i;
for (int j = 1, t; j <= pn && (t = i * pr[j]) <= MAXSN; ++j) {
npr[t] = true;
if (!(i % pr[j])) break;
}
}
}
LL n, sn, idx[MAXSN * 2 + 5];
LL f[MAXSN * 2 + 5], g[MAXSN * 2 + 5];
// #define getF(x) f[(x) <= sn ? (x) : sn + n / (x)]
inline LL& getF(const LL x) { return f[x <= sn ? x : sn + n / x]; }
// #define getG(x) g[(x) <= sn ? (x) : sn + n / (x)]
inline LL& getG(const LL x) { return g[x <= sn ? x : sn + n / x]; }
inline LL calc(const LL tn) {
if (tn <= 1) return 0;
::n = tn, ::sn = sqrt(tn), idx[0] = 0;
for (LL l = 1; l <= n; l = n / (n / l) + 1) idx[++idx[0]] = n / l;
std::sort(idx + 1, idx + idx[0] + 1);
rep (i, 1, idx[0]) getF(idx[i]) = idx[i] - 1;
rep (i, 1, pn) {
if (1ll * pr[i] * pr[i] > n) break;
per (j, idx[0], 1) {
if (1ll * pr[i] * pr[i] > idx[j]) break;
getF(idx[j]) -= getF(idx[j] / pr[i]) - (i - 1);
}
}
rep (i, 1, idx[0]) g[i] = 0;
per (i, pn, 1) {
per (j, idx[0], 1) {
if (1ll * pr[i] * pr[i] > idx[j]) break;
LL pw = pr[i], &cur = getG(idx[j]);
for (int k = 1; pw * pr[i] <= idx[j]; ++k, pw *= pr[i]) {
cur += getG(idx[j] / pw) + pr[i] * (getF(idx[j] / pw) - i + 1);
}
}
}
return getG(n);
}
int main() {
init();
LL l, r;
scanf("%lld %lld", &l, &r);
printf("%lld\n", calc(r) - calc(l - 1));
return 0;
}