min25筛学习笔记

· · 个人记录

orzhq!!

min25的功能好像和杜教筛差不多?都是处理积性函数前缀和。

但是这个min25筛她的复杂度是 O(\frac{n^{\frac 3 4}}{logn}) 的,感觉没有杜教筛优秀,但是她肯定能处理什么杜教筛不能处理的问题吧?(不懂)

基本思路?

\sum_{i=1}^n f(i) \forall x\perp y,f(xy)=f(x)f(y)

然后min25干了一件事,他把 f 的求和拆成质数和合数,然后分别考虑,就比较妙。

质数部分

\sum_{2\le p\le n} f(p)

min25采用了dp,其实就是个类似于埃氏筛的东西

我们令 g(i,j) 表示前 i 个数当中,minp(i)>p_j 的数的 F(i) 之和,其中 F(i) 满足,F(p)=f(p),且 F 为完全积性函数,且 F 的前缀和容易计算。(基本就是多项式)

转移大概就是考虑筛掉一个质数,那么我们可以把所有整除 p_j 的数挑出来,这些数当中,除了原本就是质数的数以外,其它的数都会在这一次被筛掉,又因为 F 完全积性,所以...

g(i,j)=g(i,j-1)-F(p_j)\left[g(\lfloor\frac{i}{p_j}\rfloor,j-1)-\sum_{2\le p\le p_{j-1}}F(p)\right]

从左往右解释一遍,首先我们要筛掉 p_j,那么我们就要把 p_j 提出来剩下了 1,2,3,4\dots,\lfloor \frac{i}{p_j}\rfloor。这些数当中所有已经被 j-1 筛掉的数都可以被选,但是他实际上还算到了所有 [1,j-1] 里的质数,而这些其实是应该被筛掉的,所以杀掉他。

边界条件是 p_j^2 \le n,此时显然 g 直接继承。

然后还有就是 g(n,0)=\sum_{1\le i\le n} F(i),这个可以用 F 的性质快速算出(一般都是求和公式)。

然后我们就可以递推得到 g(i,+\infty),实际上取到 \sqrt{i} 就够了。

注意到我们可以把 j 这一维滚掉。然后 i 这一维就直接整除分块吧...

合数部分

这一部分其实跟前面比较类似,不过用到的是把最小质因数拆出来。

显然 $S(n,1)+f(1)$ 就是答案。 我们考虑去枚举当前这个质数贡献了几次幂,把所有能贡献到的合数全部加回来。(感觉比质数更容易理解一点?) $$ S(i,j)=g(i,+\infty)-\sum_{k=1}^{j - 1}f(p_k)+\sum_{e\ge 1,p_k^{e+1}\le i,k\ge j}f(p_k^e)S(\frac{n}{p_k^e},k+1)+f(p_k^{e+1}) $$ 这个东西就是考虑先把质数搞出来,然后加上合数的贡献,也就是枚举所有合数当中最小质因子大于等于 $j$ 的数,然后枚举其次数把它提出来,注意到我们没有统计 $p_k^i$ 这种形式的数,那么加上就可以了。 这部分代码据说直接递归处理复杂度就是对的。 ## 实现 这个玩意感觉不是很好实现的样子... 首先我需要先想想这玩意怎么dp,首先你会发现这个东西其实有用的 $g$ 只有根号个,可以数论分块,然后我们可以把 $j$ 套在外面,这样就可以对于每个 $j$ 一遍一遍做了。 然后S直接dp应该就没什么好说的了吧.. ```cpp #include <bits/stdc++.h> using namespace std; namespace Minus { typedef long long ll; const int maxn = 1e6 + 9, N = 1e6; const ll mod = 1e9 + 7; ll n, m; int id1[maxn], id2[maxn], tot; int getid(ll x) { return x <= m ? id1[x] : id2[n / x]; } int np[maxn], ptot; ll prime[maxn]; void init() { for (int i = 2; i <= N; ++i) { if (!np[i]) prime[++ptot] = i; for (int j = 1; j <= ptot && i * prime[j] <= N; ++j) { np[i * prime[j]] = 1; if (i % prime[j] == 0) break; } } } ll g1[maxn], g2[maxn], w[maxn]; ll ksm(ll a, ll b) { ll ret = 1; for (; b; b >>= 1, a = a * a % mod) if (b & 1) ret = ret * a % mod; return ret; } const ll inv2 = ksm(2, mod - 2), inv6 = ksm(6, mod - 2); ll sum1[maxn], sum2[maxn]; ll f(ll x) { return x * (x - 1 + mod) % mod; } ll getans(ll i, int j) { if (prime[j] > i || i <= 1) return 0; int x = getid(i); ll ret = (((g2[x] - g1[x] + mod) % mod) - ((sum2[j - 1] - sum1[j - 1] + mod) % mod) + mod) % mod; for (int k = j; k <= ptot && prime[k] * prime[k] <= i; ++k) { ll now1 = prime[k], now2 = prime[k] * prime[k]; for (; now2 <= i; now1 = now2, now2 *= prime[k]) { ret = (ret + (f(now1 % mod) * getans(i / now1, k + 1) % mod + f(now2 % mod)) % mod) % mod; } } return ret; } int main() { init(); scanf("%lld", &n); m = sqrt(n); for (ll l = 1, r; l <= n; l = r + 1) { // 搞出来所有g的初始 r = n / (n / l); w[++tot] = (n / l); if ((n / l) <= m) id1[w[tot]] = tot; else id2[n / w[tot]] = tot; ll v = w[tot] % mod; g1[tot] = v * (v + 1) % mod * inv2 % mod; g2[tot] = v * (v + 1) % mod * (2 * v % mod + 1) % mod * inv6 % mod; g1[tot] = (g1[tot] - 1 + mod) % mod; // 别忘了干掉1 g2[tot] = (g2[tot] - 1 + mod) % mod; } for (int j = 1; j <= ptot; ++j) { sum1[j] = (sum1[j - 1] + prime[j]) % mod; sum2[j] = (sum2[j - 1] + prime[j] * prime[j] % mod) % mod; } for (int j = 1; prime[j] <= m; ++j) { for (int i = 1; i <= tot && prime[j] * prime[j] <= w[i]; ++i) { g1[i] = (g1[i] - prime[j] * ((g1[getid(w[i] / prime[j])] - sum1[j - 1] + mod) % mod) % mod + mod) % mod; g2[i] = (g2[i] - prime[j] * prime[j] % mod * ((g2[getid(w[i] / prime[j])] - sum2[j - 1] + mod) % mod) % mod + mod) % mod; } } // printf("%lld\n", (g2[1] - g1[1] + mod) % mod); printf("%lld\n", (getans(n, 1) + 1) % mod); return 0; } } int main() { return Minus::main(); } ```