洲阁筛 & min_25筛学习笔记
洲阁筛
给定一个积性函数$F(n)$,求$\sum_{i = 1}^{n}F(n)$。并且$F(n)$满足在素数和素数次幂的时候易于计算。
显然有:
$\sum_{i = 1}^{n} F(n) = \sum_{i = 1}^{\sqrt{n}}F(i) \left(\sum_{\sqrt{n} < p\leqslant n/i, p\ is\ a\ prime} F(p) \right) + \sum_{i = 1, i\ has\ no\ prime\ factor\ greater\ than\ \sqrt{n}}^{n} F(i)$。
Part 1
第一部分是要求出$\sum_{\sqrt{n}<p \leqslant n/i, p\ is\ a\ prime} F(p)$
假设小于等于$\sqrt{n}$的素数从小到大分别是$P_1, P_2, \cdots, P_{m}$。
设$f_{i, j}$表示$[1, j]$中和前$i$个素数互质的数的函数值和。
当$i = 0$时候想办法计算。
考虑当$i > 0$的时候的转移。考虑从$f_{i - 1, j}$中需要删掉最小质因子等于$P_i$的数的贡献。
所以有转移式子$f_{i, j} = f_{i - 1, j} - F(P_i) f_{i - 1, \left \lfloor j / P_i \right \rfloor}$
如果$F(n)$本身不是完全积性函数,需要把它拆成若干积性函数的和。注意这一部分只用保证素数处取值相同即可。
注意到第一维约有$O\left (\frac{\sqrt{n}}{\log n} \right)$种取值,第二维有$O\left (\sqrt{n}\right)$种取值。暴力计算的复杂度是$O\left (\frac{n}{\log n} \right )$
优化
考虑当$j < P_{i+ 1}$的时候,$f_{i, j} = 1$。考虑把条件放缩成$j < P_i$。
因此,当$P_i \leqslant j < P_{i}^2$的时候$f_{i, j} = f_{i - 1, j} - F(P_i)$。
所以做法是:
- 维护一个$F(P_i)$的前缀和
- 考虑转移时的$f_{i - 1, \left \lfloor j / P_i \right \rfloor}$
- 当$j \geqslant P_{i}^2$的时候暴力计算
- 当$P_i \leqslant j < P_{i}^2$的时候,需要这样的状态的时候减去一段和就行了。
- 当$j < P_i$的时候,由于$f_{i, 0} = 0$,所以这一部分的值不会改变。
所以对于每个$j$,有效的状态只有$O\left (\frac{\sqrt{j}}{\log j} \right )$。
时间复杂度为:$\sum_{i = 1}^{\sqrt{n}} O \left (\frac{\sqrt{i}}{\log i} \right ) + \sum_{i = 1}^{\sqrt{n}} O\left ( \frac{\sqrt{n/i}}{\log {n/i}}\right)$
由于第一部分显然小于第二部分,所以只用考虑第二部分的和。
所以有:$\sum_{i = 1}^{\sqrt{n}}O \left (\frac{\sqrt{n/i}}{\log n - \log i}\right)$
由于$\log i \leqslant \log \sqrt{n} = \frac{1}{2}\log n$
所以:$\sum_{i = 1}^{\sqrt{n}}O \left (\frac{\sqrt{n/i}}{\frac{1}{2}\log n}\right) = \sum_{i = 1}^{\sqrt{n}}O\left (\frac{\sqrt{n / i}}{\log n}\right) = O\left ( \frac{n^{3/4}}{\log n}\right)$
Part 2
这一部分是要求出$\sum_{i = 1, i\ has\ no\ prime\ factor\ greater\ than\ \sqrt{n}}^{n} F(i)$
假设小于等于$\sqrt{n}$的素数从小到大分别是$P_1, P_2, \cdots, P_{m}$。
设$g_{i, j}$表示在$[1, j]$中,质因子只包含$P_{i}, P_{i + 1}, \cdots, P_{m}$的数的函数值的和。
转移考虑枚举当前质数$P_i$的指数。所以有:$g_{i, j} = g_{i + 1, j} + \sum_{e = 1}^{\log_{P_i} j} F(P_{i}^{e}) g_{i + 1, \lfloor j / P_i^e\rfloor}$
对于时间复杂度,考虑对于每一个$j$的转移的枚举量,可以得到大概是$O(\frac{\sqrt{j}}{\log j})$。(不会积分,告辞)
和Part 1一样,得到暴力计算这一部分的复杂度是$O(\frac{n}{\log n})$。
优化
因为质因子是从大到小枚举的,所以当$j < P_{i}$的时候,$g_{i, j} = 1$。
所以当满足$P_i \leqslant j < P_{i}^2$,满足$g_{i, j} = g_{i + 1, j} + F(P_i)$。
和Part 1一样,分成三段,一段暴力转移,一段需要时前缀和,一段不需要维护。
复杂度也是同样的计算方法
1 /** 2 * SPOJ 3 * Problem#DIVCNT3 4 * Accepted 5 * Time: 35190ms 6 * Memory: 33792k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 template <typename T> 13 void pfill(T* pst, const T* ped, T val) { 14 for ( ; pst != ped; *(pst++) = val); 15 } 16 17 #define ll long long 18 19 typedef class Euler { 20 public: 21 int num; 22 boolean *vis; 23 int *pri, *d3, *mp; 24 25 Euler(int n) : num(0) { 26 d3 = new int[(n + 1)]; 27 mp = new int[(n + 1)]; 28 pri = new int[(n + 1)]; 29 vis = new boolean[(n + 1)]; 30 pfill(vis, vis + n + 1, false); 31 d3[1] = 1; 32 for (int i = 2; i <= n; i++) { 33 if (!vis[i]) { 34 d3[i] = 4, pri[num++] = i, mp[i] = 1; 35 } 36 for (int j = 0, _ = n / i, x; j < num && pri[j] <= _; j++) { 37 vis[x = pri[j] * i] = true; 38 if (!(i % pri[j])) { 39 d3[x] = (d3[i / mp[i]] + 3) * d3[mp[i]]; 40 mp[x] = mp[i]; 41 break; 42 } else { 43 mp[x] = i, d3[x] = d3[i] << 2; 44 } 45 } 46 } 47 } 48 ~Euler() { 49 delete[] mp; 50 delete[] vis; 51 delete[] d3; 52 delete[] pri; 53 } 54 } Euler; 55 56 const int N = 330000; 57 58 Euler *_euler; 59 60 int T; 61 vector<ll> ns; 62 63 ll maxn, D; 64 65 int cnt_prime; 66 int *pri, *d3; 67 68 inline void init() { 69 scanf("%d", &T); 70 for (ll x; T--; ) { 71 scanf("%lld", &x); 72 ns.push_back(x); 73 maxn = max(maxn, x); 74 } 75 D = sqrt(maxn + 0.5) + 1; 76 while (D * 1ll * D <= maxn) 77 D++; 78 _euler = new Euler(D + 23); 79 cnt_prime = _euler->num; 80 pri = _euler->pri, d3 = _euler->d3; 81 } 82 83 ll n; 84 int cnt_P[N]; 85 int range0[N], range1[N]; 86 ll f0[N], f1[N], g0[N], g1[N]; 87 88 void precalc() { 89 for (int i = 1; i < D; i++) { 90 range0[i] = range0[i - 1]; 91 while (pri[range0[i]] * 1ll * pri[range0[i]] <= i) { 92 range0[i]++; 93 } 94 } 95 for (int i = D - 1; i; i--) { 96 ll y = n / i; 97 range1[i] = range1[i + 1]; 98 while (pri[range1[i]] * 1ll * pri[range1[i]] <= y) { 99 range1[i]++; 100 } 101 } 102 pfill(cnt_P, cnt_P + D, 0); 103 for (int i = 0; pri[i] < D; i++) { 104 cnt_P[pri[i]]++; 105 } 106 for (int i = 1; i < D; i++) { 107 cnt_P[i] += cnt_P[i - 1]; 108 } 109 } 110 111 int nump; 112 ll get_value_f(int i, ll j) { 113 if (j >= D) { 114 int rj = n / j; 115 return f1[rj] + (max(0, nump - max(range1[rj], i)) << 2); 116 } 117 return f0[j] + (max(0, cnt_P[j] - max(range0[j], i)) << 2); 118 } 119 120 void calculate_f() { 121 for (nump = 0; pri[nump] < D; nump++); 122 for (int i = 1; i < D; i++) { 123 f0[i] = 1, f1[i] = 1; 124 } 125 for (int i = nump - 1; ~i; i--) { 126 for (int j = 1; j < D && i < range1[j]; j++) { 127 ll m = n / j, coef = 1; 128 while (m /= pri[i]) { 129 f1[j] += (coef += 3) * get_value_f(i + 1, m); 130 } 131 } 132 for (int j = D - 1; j && i < range0[j]; j--) { 133 ll m = j, coef = 1; 134 while (m /= pri[i]) { 135 f0[j] += (coef += 3) * get_value_f(i + 1, m); 136 } 137 } 138 } 139 for (int i = 1; i < D; i++) { 140 f1[i] = get_value_f(0, n / i); 141 } 142 for (int i = 1; i < D; i++) { 143 f0[i] = get_value_f(0, i); 144 } 145 } 146 147 ll get_value_g(int i, ll j) { 148 if (j >= D) { 149 int rj = n / j; 150 return g1[rj] - max(0, i - range1[rj]); 151 } 152 return g0[j] - max(0, min(i, cnt_P[j]) - range0[j]); 153 } 154 155 void calculate_g() { 156 for (int i = 1; i < D; i++) { 157 g0[i] = i, g1[i] = n / i; 158 } 159 int cp = 0; 160 for (int i = 0; pri[i] < D; i++, cp++) { 161 for (int j = 1; j < D && i < range1[j]; j++) { 162 g1[j] -= get_value_g(i, n / j / pri[i]); 163 } 164 for (int j = D - 1; j && i < range0[j]; j--) { 165 g0[j] -= get_value_g(i, j / pri[i]); 166 } 167 } 168 for (int i = 1; i < D; i++) { 169 (g1[i] = get_value_g(cp, n / i) - 1) <<= 2; 170 } 171 for (int i = 1; i < D; i++) { 172 (g0[i] = get_value_g(cp, i) - 1) <<= 2; 173 } 174 } 175 176 void Solve(ll _n) { 177 n = _n; 178 D = sqrt(n + 0.5) + 1; 179 while (D * 1ll * D <= n) 180 D++; 181 precalc(); 182 calculate_g(); 183 calculate_f(); 184 ll ans = f1[1]; 185 for (int i = 1; i < D; i++) { 186 ans += d3[i] * g1[i]; 187 // cerr << d3[i] << " " << g1[i] << '\n'; 188 } 189 printf("%lld\n", ans); 190 } 191 192 inline void solve() { 193 for (int i = 0; i < (signed) ns.size(); i++) { 194 Solve(ns[i]); 195 } 196 } 197 198 int main() { 199 init(); 200 solve(); 201 return 0; 202 }
Min25筛
Part 1
假设小于等于$\sqrt{n}$的素数从小到大分别是$P_1, P_2, \cdots, P_{m}$。
设$g_{i, j} = \sum_{k = 2}^{j} [k的最小质因子大于P_i或者k是质数] F(k)$
转移式子:
$$g_{i, j} =
\begin{cases}g_{i - 1, j} - F(P_i)(g_{i - 1, \lfloor j / P_i\rfloor} - g_{i - 1, P_i - 1}) & (j \geqslant P_i^2) \\
g_{i - 1, j} & (1 \leqslant j < P_i^2)
\end{cases}
$$
和洲阁筛类似,不过第一种情况还需要抠掉素数的贡献。
这里也要求$F$是一个完全积性函数。
我们考虑求出$1, 2, \cdots, \sqrt{n}, \lfloor n / \sqrt{n}\rfloor, \cdots, \lfloor n / 2\rfloor, n$处的值。
Part 2
这里考虑计算$S(n, i) = \sum_{i = 2}^{n}[n的最小质因子大于等于P_i]F(n)$。
那么显然答案等于$S(n, 1) + F(1)$。
这里简单粗暴一点,如果是素数,那么可以用第一部分计算的答案再减去某个前缀和得到,否则枚举这个合数的最小质因子。不难得到这样一个式子:
$$
S(n, i) = g_{m, n} - \sum_{j = 1}^{i - 1}F(P_j) + \sum_{j = i \wedge P_j^2 \leqslant n}\sum_{e = 1\wedge P_{j}^{e + 1}\leqslant n} F(P_{j}^{e}) S(\lfloor n/P_{j}^e\rfloor, j + 1) + F(P_{j}^{e + 1})
$$
前一半是计算素数的贡献。
考虑计算合数的贡献,后一半是先枚举最小质因子,因为它是一个合数最小质因子,所以$p^2 \leqslant n$
$p^{e + 1} \leqslant n$是类似的原因。
时间复杂度被证明为是$O(\frac{n}{Poly(\log n)})$。
(似乎目前min_25在$10^{13}$范围内吊打其他筛)
/** * loj * Problem#6053 * Accepted * Time: 332ms * Memory: 2684k */ #include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 1e9 + 7; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const Zi inv2 ((Mod + 1) >>1); const int Dmx = 1e5 + 5; ll n; int D; int pnum; int pri[Dmx], sf[Dmx]; void Euler(int n) { static bitset<Dmx + 23> vis; for (int i = 2; i <= n; i++) { if (!vis.test(i)) { pri[pnum++] = i; } for (int *p = pri, *_ = pri + pnum, x; p != _ && (x = *p * i) <= n; p++) { vis.set(x); if (!(i % *p)) { break; } } } for (int i = 1; i <= pnum; i++) { sf[i] = sf[i - 1] + (pri[i - 1] ^ 1); } } template <typename T> class SieveArray { public: ll n; vector<T> a0; vector<T> a1; void init(ll n, int D) { this->n = n; a0.assign(D, 0); a1.assign(D, 0); } T& operator [] (ll p) { return (p >= (signed) a0.size()) ? a1[n / p] : a0[p]; } }; SieveArray<int> range; SieveArray<Zi> f0, f1; Zi S(ll n, int m) { if (pri[m] > n) { return 0; } Zi rt = f1[n] - sf[m]; for (int j = m; j < pnum && 1ll * pri[j] * pri[j] <= n; j++) { int p = pri[j]; ll nn = n, p2 = 1ll * p * p; for (int e = 1; p2 <= nn; e++) { rt += S(nn /= p , j + 1) * (p ^ e) + (p ^ (e + 1)); } } return rt; } int main() { scanf("%lld", &n); D = sqrt(D + 0.5) + 1; while (1ll * D * D <= n) D++; Euler(D + 3); range.init(n, D); f0.init(n, D); f1.init(n, D); ll t = 0; range[0] = 0; for (int i = 1; i < D; i++) { while (t * t <= i) t++; range.a0[i] = t; } t = 0; for (int i = D; --i; ) { ll v = n / i; while (t * t <= v) t++; range.a1[i] = t; } for (int i = 1; i < D; i++) { f0.a0[i] = i - 1; f1.a0[i] = ((Zi(i) * (i + 1)) * inv2) - 1; ll v = n / i; f0.a1[i] = v - 1; f1.a1[i] = ((Zi(v) * (v + 1)) * inv2) - 1; } for (int i = 0; i < pnum; i++) { int p = pri[i]; if (p >= D) { break; } ll v; for (int j = 1; j < D && p < range.a1[j]; j++) { v = n / (1ll * j * p); f0.a1[j] -= f0[v] - f0.a0[p - 1]; f1.a1[j] -= (f1[v] - f1.a0[p - 1]) * p; } for (int j = D; p < range.a0[--j]; ) { f0.a0[j] -= f0.a0[j / p] - f0.a0[p - 1]; f1.a0[j] -= (f1.a0[j / p] - f1.a0[p - 1]) * p; } } for (int i = 1; i < D; i++) { f1.a0[i] -= f0.a0[i]; f1.a0[i] += (i >= 2) * 2; f1.a1[i] -= f0.a1[i]; f1.a1[i] += (2 * i <= n) * 2; } Zi ans = S(n, 0) + 1; printf("%d\n", ans.v); return 0; }