Min_25筛
Min_25筛是一种优秀的求积性函数前缀和的方法, 时间复杂度\(O\left(\frac{n^{\frac{3}{4}}}{\log}\right)\), 空间复杂度\(O(\sqrt{n})\).
记号约定
\(p\)一般表示一个质数, \(L=\sqrt{n}\), \(P\) 表示 $\le L $ 质数集, 其中 \(P_i\) 表示第 \(i\) 大的质数.
使用条件
\(f(p)\)是一个低次多项式;
\(f(p^x )\)可以快速计算;
对于一个多项式, 我们将每一项拆开, 分别做min_25筛. 要满足每一项是积性函数.
Part I. 质数部分
(以下关于合数的函数计算就假装它是质数, 把函数当成完全积性函数,方便计算, 反正我们只要质数部分的答案QAQ)
设\(g(n,j)\)表示, 在小于等于\(n\)的数\(i\)中, 满足本身是质数或者最小质因子大于\(P_j\)的所有\(f(i)\)之和.
特别的, \(g(n,0)\)表示\(x=2\dots n\)的\(f(x)\)和.
(其实就是筛质数时筛完第\(i\)个质数后剩下的没有被筛掉的数)
递推\(g(n,j)\):
考虑从\(g(n,j-1)\)转移过来. \(g(n,j-1)\)比\(g(n,j)\)多算的部分其实就是最小质因子为\(P_j\)的合数.
(上面的\(f(P_j)\)可以直接乘后面的部分是因为上面提到的我们是当做完全积性函数计算的)
边界\(g(n,0)\)即为自然数幂和.
可以直接递归求, 也可以求出所有的\(\lfloor \frac{n}{i}\rfloor\), 直接离散后存在一维数组中(由于只会转移到更大的数, 所以一维就够了); 对于\(g(P_j-1,j-1)\), 即小于\(P_j\)的所有质数的函数值, 可以直接预处理.
如果要求所有质数的函数值, 那么答案就是\(g(n,L)\).(因为\(g(n,j)\)的定义是关于最小质因子, 一个合数的最小质因子一定不大于\(\sqrt{n}\))
Part II. 合数部分
设\(S(n,j)\)表示\(n\)以内的所有数中最小质因子大于等于\(P_j\)的所有数的函数值之和.
那么有转移:
\(S(n,j)\)中的质数部分即\(g(n,j-1)\)除掉小于\(P_j\)的质数的函数值, 这里的\(g(P_j - 1, j-1)\)和上面一样是可以预处理的.
对于合数部分, 我们枚举它的最小质因子\(P_k\)以及它的次数\(e\)递归到下一层, 但是这样会少算\(f(P_k^e)\)的贡献, 加上就是了.
\(S(n,1)+f(1)\)即为答案.
例题
SPOJ DIVCNTK
Description
求 $$S_k(n) = \sum_{i=1}^n \sigma_0(i^k)$$
对\(2^{64}\)取模, \(n\le 10^{10}\).
Solution
设\(f(x) = \sigma_0(x^k)\), 那么有:
\(f(p^e) = ke + 1\)且\(f(x)\)为积性函数, 直接Min_25筛即可.
Code
const int MAXS = 1e6 + 5;
LL n;
ULL K;
int s;
int pr[MAXS], prs;
void sieve(int n) {
static int np[MAXS];
For(i, 2, n) {
if (!np[i]) pr[++ prs] = i;
for (int j = 1; j <= prs && pr[j] * i <= n; ++ j) {
np[i * pr[j]] = 1;
if (i % pr[j] == 0) break;
}
}
}
int lss, id1[MAXS], id2[MAXS];
LL ls[MAXS << 1];
ULL g[MAXS << 1];
#define id(x) ((x) <= s ? id1[x] : id2[n / (x)])
void calc_g() {
lss = 0;
for (LL l = 1, r = 0; r < n; l = r + 1) {
r = n / (ls[++ lss] = n / l);
g[lss] = ls[lss] - 1, id(ls[lss]) = lss;
}
For(i, 1, prs) {
LL bot = (LL)pr[i] * pr[i];
if (bot > n) break;
for (int j = 1; j <= lss && bot <= ls[j]; ++ j)
g[j] -= g[id(ls[j] / pr[i])] - (i - 1);
}
}
ULL S(LL m, int j) {
if (m <= 1 || pr[j] > m) return 0;
ULL ans = (g[id(m)] - (j - 1)) * (K + 1);
for (int k = j; k <= prs && (LL)pr[k] * pr[k] <= m; ++ k)
for (LL prod = pr[k], e = 1; prod * pr[k] <= m; prod *= pr[k], ++ e)
ans += S(m / prod, k + 1) * (K * e + 1) + (K * (e + 1) + 1);
return ans;
}
int main() {
#ifdef hany01
freopen("DIVCNTK.in", "r", stdin);
freopen("DIVCNTK.out", "w", stdout);
#endif
sieve(1e6);
for (int T = read<int>(); T --; ) {
s = sqrt(n = read<LL>()), K = read<ULL>();
calc_g(), printf("%llu\n", S(n, 1) + 1);
}
return 0;
}
一些技巧与启发
1.对于质数部分, Min_25筛每次将合数的最小质因子筛去. 我们可以利用这个性质处理一些有关最小质因子的问题.
2.对于合数部分, 我们每次从小到大枚举质因子, 我们可以对于枚举的质因子加上一些约束(注意同时修改质数部分), 也可以对最大质因子,次大质因子进行计算.(UOJ188)
3.筛数时可以考虑每个数的最小质因子, 因为合数的最小质因子是\(\le \sqrt{n}\)的.