用途&复杂度

min25筛可用于计算一些积性函数的前缀和。

要求:f(p)f(p),(pp 为质数),是一个关于 pp 的项数较少的多项式,或可以快速求值。f(pc)f(p^c) 可以快速求值。

复杂度:O(n34logn)O(\frac{n^\frac{3}{4}}{\log n})

min25筛与杜教筛都可以计算积性函数的前缀和,且新的min25筛复杂度也是 O(n23)O(n^{\frac{2}{3}})。但是使用杜教筛需要构造出一个积性函数 g(i)g(i),使得 fgf*g 可以快速求出前缀和。而使用min25筛则只要满足上面的要求即可。

 

原理

f(i)f(i) 为题中需要求前缀和的积性函数,pp 为某个质数。

分为两步:

  1. 对于所有 x=ndx=\lfloor \frac{n}{d}\rfloor,(也就是数论分块里的那个数),求出不超过 xx 的所有质数的 ff 值之和。即 pxf(p)\sum_{p\le x} f(p)
  2. 求解原问题,即对于所有 x=ndx=\lfloor \frac{n}{d}\rfloor,求出不超过 xx 的所有数的 ff 值之和。即 i=1xf(i)\sum_{i=1}^x f(i)

 

预处理:

需要先处理出小于等于 n\sqrt{n} 的所有质数。

PiP_i 表示从小到大第 ii 个质数,P1=2P_1=2

 

第一步

g(i)=f(i)g(i)=f(i)。为啥还要再搞一个?因为这里的 ii 是任意数(包括非质数),也就是说把任意数都套进 f(i)f(i)ii 为质数 里。而实际上真正的 ff 在非质数上已经有定义了,且很可能不等于质数时的式子,所以这里另设了一个 gg

lpf(i)lpf(i)ii 的最小质因数。

G(i,j)G(i,j)i\le i 的所有质数以及最小质因子 >Pj>P_j 的合数的 gg 值之和。即 k=1ig(k)[kprimelpf(k)>Pj]\sum_{k=1}^ig(k)[k\in prime||lpf(k)>P_j]

则有对于任意 iiG(i,0)=k=2ig(k)G(i,0)=\sum_{k=2}^i g(k)

第一步的目的就是求 G(i,j)G(i,j)

考虑递推从 G(n,j1)G(n,j-1) 求出 G(n,j)G(n,j)

j1j-1 变为 jj 少了那些以 PjP_j 为最小质因子的合数的 gg 值。

  • 如果 Pj2>nP_j^2>n,则不存在以 PjP_j 为最小质因数的合数,结果也就没有变化,所以 G(n,j)=G(n,j1)G(n,j)=G(n,j-1)

  • 如果 Pj2nP_j^2\le n,所有要去掉的合数的最小质因子都是 PjP_j,把 PjP_j 提出来,则需要减去的部分是 g(Pj)G(nPj,j1)g(P_j)\cdot G(\frac{n}{P_j},j-1),但是别忘了,G(n,j)G(n,j) 中还包含了所有小于等于 nn 的质数,这些质数不应该被去掉,所以还要再补回来,即加上 i=1j1g(Pi)\sum_{i=1}^{j-1}g(P_i),也可以写成 G(Pj1,j1)G(P_{j-1},j-1)

其实这里还有个问题,为什么可以把 PjP_j 提出来?但是我也不会 大概是因为最开始限定了g(p)=f(p)g(p)=f(p)pp 的低次多项式,参考 OIWiKi 。或者把 f(p)f(p) 实例化成 psp^s,理解起来方便些。

把上面两种情况合并,又由于 g(p)=f(p)g(p)=f(p)

综上

G(i,0)=k=2ig(k)G(i,0)=\sum_{k=2}^i g(k)

G(n,j)={G(n,j1)Pj2>nG(n,j1)f(Pj)[G(nPj,j1)i=1j1f(Pi)]Pj2nG(n,j)= \begin{cases} G(n,j-1)&P_j^2\gt n\\ G(n,j-1)-f(P_j)[G(\frac{n}{P_j},j-1)-\sum_{i=1}^{j-1}f(P_i)]&P_j^2\le n\\ \end{cases}

 

第二步

S(i,j)S(i,j) 表示 i\le i 的所有最小质因子大于等于 PjP_j 的数的 ff 值之和。这里令 lcp(p)=plcp(p)=p,即质数的最小质因子为它自己。

所以 SS 同样可以分成质数和合数两部分。

 

先考虑质数部分。

由第一步可以求出 G(n,p)G(n,|p|),其中 p|p| 为小于等于 n\sqrt{n} 的质数个数,在预处理部分可以得到。G(n,p)G(n,|p|) 实际上是所有质数的 ff 值之和,即 pf(p)\sum_p f(p),但是我们需要的是大于等于 PjP_j 的质数,所以要减去 i=1j1f(Pi)\sum_{i=1}^{j-1}f(P_i) 。所以质数部分的结果为

G(n,P)i=1j1f(Pi)G(n,|P|)-\sum_{i=1}^{j-1}f(P_i)\\

 

再考虑合数部分。

对于每个合数,枚举它的最小质因子,以及包含这个最小质因子的次数。

kje1f(Pke)S(nPke,k+1)\sum_{k\ge j}\sum_{e\ge 1} f(P_k^e)S(\frac{n}{P_k^e},k+1)\\

要注意,这里可以提出来和上面的原因不同,这里是因为已经限制了剩下的部分最小质因子大于等于 Pk+1P_{k+1}, 所以是互质的,由于是积性函数,所以可以直接把结果相乘。

但是,如果一个数只有一个质因子? x=pkx=p^k ?所以还要再补上一部分 f(Pke+1)f(P_k^{e+1})。为啥是 e+1e+1 ?因为 p1p^1 是质数,之前算过了,而 Pie+1P_i^{e+1} 一定是 n\le n 的,也需要加进来,所以就是 e1f(Pke+1)\sum_{e\ge 1}f(P_k^{e+1}).

综上,合数部分真正的结果是

kje1f(Pke)S(nPke,k+1)+f(Pke+1)\sum_{k\ge j}\sum_{e\ge 1} f(P_k^e)S(\frac{n}{P_k^e},k+1)+f(P_k^{e+1})\\

 

至此,第二步的递推式也得到了。

S(i,P)=G(i,P)S(i,|P|)=G(i,|P|)\\

S(n,j)=G(n,P)i=1j1f(Pi)+kje1f(Pke)S(nPke,k+1)+f(Pke+1)S(n,j)=G(n,|P|)-\sum_{i=1}^{j-1}f(P_i)+\sum_{k\ge j}\sum_{e\ge 1} f(P_k^e)S(\frac{n}{P_k^e},k+1)+f(P_k^{e+1})\\

 

最后一步

虽然两步的式子都推出来了,但是如果要实现的话,需要枚举每一个 ini\le njPj\le |P|ii 的复杂度显然不行。

但是可以发现每次 nn 出现的地方都是 nx\lfloor\frac{n}{x}\rfloor,所以其实只需要计算 G(nx)G(\lfloor\frac{n}{x}\rfloor),且递归求 SS 的过程中也只涉及到了 nx\lfloor\frac{n}{x}\rfloor。数论分块告诉我们这个的个数是 O(n)O(\sqrt{n}) 的。

最终答案就是 S(n,1)+f(1)S(n,1)+f(1),因为 1 的值之前一直没算。

 

再来理一遍流程。

首先预处理出所有 n\le\sqrt{n} 的质数,同时得到它们的前缀和(指只有质数的和)。

由于我们只需要 G(nx,j)G(\lfloor\frac{n}{x}\rfloor,j),所以数论分块,对于每个 i=nxi=\lfloor\frac{n}{x}\rfloor,快速求出 G(i,0)G(i,0),也就是把 ff 中质数的式子应用到任意数时的前缀和。再通过第一步的递推式求出所有 G(nx,j)G(\lfloor\frac{n}{x}\rfloor,j)。其目的是为了得到 G(i,P)G(i,|P|),作为第二步递推的初始值。

由此也可以看出,我们求第一步的目的就在于求出所有质数的 ff 的前缀和。而也有题目也仅仅要求 ff 在质数时的前缀和,这时就只需要求第一步了。

最后通过第二步的递推式求出 S(n,1)S(n,1)。答案为 S(n,1)+f(1)S(n,1)+f(1)

 

例题

LOJ#6053 简单的函数

题意:ff 为积性函数,且 f(pc)=pcf(p^c)=p\oplus c,求前缀和。

这里实际上是求了素数个数以及素数和。

先考虑质数的 ff 值。f(p)=p1f(p)=p\oplus 1,由于除了 2 外的质数都是奇数,这时 g(p)=f(p)=p1g(p)=f(p)=p-1,这个 gg 不是积性函数,不能直接筛,所以要把 f(p)f(p) 拆成 f1(p)=p,f2(p)=1f_1(p)=p,f_2(p)=1,则 f(p)=f1(p)+f2(p)f(p)=f_1(p)+f_2(p)

f1f_1 就是素数和,f2f_2 就是素数个数,分别求出 G1,G2G_1,G_2,再代入第二步求 SS

要注意维护 nx\lfloor \frac{n}{x} \rfloor 时由于可能会过大,所以要存在 nx\lfloor \frac{n}{x} \rfloorn/nxn/\lfloor \frac{n}{x} \rfloor 这两者中较小的下标处,这只是为了存下,与用map存一样,但是map会带来额外的 log\log,所以不行。

p=2p=2 时,f(p)=3f(p)=3,但是我们算的是 1,所以当 j=1j=1,即包含质因子 2 时,res 要加 2。

最后要加上 f(1)f(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) cout << #x << ":\t" << x << endl;
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const ll inv2 = (mod + 1) / 2;
ll n, sqr;
int vis[N], tot;
ll prime[N], sump[N];
void pre(ll n) {
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++tot] = i;
sump[tot] = (sump[tot - 1] + i) % mod;
}
ll d;
for (int j = 1; j <= tot && (d = i * prime[j]) <= n; j++) {
vis[d] = 1;
if (i%prime[j] == 0)break;
}
}
}
ll w[N]; int m;
ll g[N], h[N];
int id[2][N];
ll f(ll x, int y) { //题目要对积性函数f求前缀和
return x ^ y;
}
ll S(ll x, int j) {
if (x <= 1 || x < prime[j])return 0;
int k = (x <= sqr ? id[0][x] : id[1][n / x]);
ll res = (g[k] - sump[j - 1] - h[k] + j - 1 + 2 * mod) % mod;
//if (j == 1)res = (res + 2) % mod; //这题n=2时计算结果不对,要修正
//其它题目不需要
for (int i = j; i <= tot && prime[i] * prime[i] <= x; i++) {
ll t = prime[i];
for (int e = 1; t*prime[i] <= x; e++, t *= prime[i]) {
res = (res + f(prime[i], e)*S(x / t, i + 1) % mod + f(prime[i], e + 1)) % mod;
}
}
return res;
}
int main() {
scanf("%lld", &n);
sqr = (ll)sqrt(n);

//预处理
pre(sqr);

//第一步
//求G(n/x, 0)
for (ll i = 1, j; i <= n; i = j + 1) {
j = n / (n / i);
w[++m] = n / i;
if (w[m] <= sqr)id[0][w[m]] = m;
else id[1][n / w[m]] = m;
g[m] = w[m] % mod*((w[m] + 1) % mod) % mod*inv2 % mod; //g := f_1
g[m] = (g[m] - 1 + mod) % mod;
h[m] = (w[m] - 1 + mod) % mod; //h := f_2
}
//递推求G(n/x,|P|)
for (int j = 1; j <= tot; j++) {
for (int i = 1; i <= m && w[i] >= prime[j] * prime[j]; i++) {
int k = (w[i] / prime[j] <= sqr ? id[0][w[i] / prime[j]] : id[1][n / (w[i] / prime[j])]);
ll tmp = prime[j]*(g[k] + mod - sump[j - 1]) % mod;
g[i] = (g[i] + mod - tmp) % mod;
tmp = (h[k] - (j - 1) + mod) % mod;
h[i] = (h[i] + mod - tmp) % mod;
}
}

//第二步
printf("%lld\n", (S(n, 1) + 1) % mod);
return 0;
}

 

HDU#6889 Graph Theory Class

2020CCPC网络赛1002

题意:节点 1 到 n,连边的边权为 lcm(i+1,j+1)lcm(i+1,j+1),求最小生成树。

图论部分直接跳过。

最终转化为求 i=3n+1i+p=3np[pprime]\sum_{i=3}^{n+1}i+\sum_{p=3}^n p[p\in prime]。也就是要求质数和。

不能取模!取模会超时!!素数和不会爆ll。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) cout << #x << ":\t" << x << endl;
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll mod = 1e9 + 7;
ll inv2 = (mod + 1) / 2;
int T;
ll n, sqr;
int vis[N], tot;
ll prime[N], sump[N];
void pre(ll n) {
tot = 0;
for (int i = 1; i <= n; i++)vis[i] = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++tot] = i;
sump[tot] = (sump[tot - 1] + i);
}
ll d;
for (int j = 1; j <= tot && (d = i * prime[j]) <= n; j++) {
vis[d] = 1;
if (i%prime[j] == 0)break;
}
}
}
ll g[N], w[N]; int m, id[2][N];
int main() {
pre(200000);
scanf("%d", &T);
while (T--) {
scanf("%lld%lld", &n, &mod);
n++;
sqr = (ll)sqrt(n);
inv2 = (mod + 1) / 2;
ll ans = (3 + n) % mod*((n - 2) % mod) % mod*inv2%mod;
m = 0;
for (ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
w[++m] = n / l;
if (w[m] <= sqr)id[0][w[m]] = m;
else id[1][n / w[m]] = m;
g[m] = w[m] * (w[m] + 1) / 2 - 1;
}
for (int j = 1; j <= tot; j++) {
for (int i = 1; i <= m && w[i] >= prime[j] * prime[j]; i++) {
int k = (w[i] / prime[j] <= sqr ? id[0][w[i] / prime[j]] : id[1][n / (w[i] / prime[j])]);
ll tmp = prime[j] * (g[k] - sump[j - 1]);
g[i] = g[i] - tmp;
}
}
ans = (ans + g[1] - 2 + mod) % mod;
printf("%lld\n", ans);
}
return 0;
}