Definition
From the perspective of the ideas behind this sieve, it is also called the “Extended Eratosthenes Sieve.”
Because it was invented and first used by Min\_25, it is called the “Min\_25 sieve.”
Properties
It can compute prefix sums of a class of multiplicative functions in or time.
The requirements are that is a sum of completely multiplicative functions that can be evaluated quickly as a function of (for example, polynomials), and that can be evaluated quickly.
Notation
- Unless otherwise stated, every variable denoted by in this section ranges over all primes.
- , namely, when is prime, its value is , and otherwise it is .
- : the -th smallest prime among all primes (for example, ). In particular, let .
- , that is, the smallest prime factor of . In particular, when , its value is .
Explanation
From the definition of , we can see that the answer is .
Let us consider how to compute . Enumerating the smallest prime factor of each and its exponent gives the recurrence:
The last derivation is based on the following fact: when holds for , we have , so . Its boundary value is .
Suppose that all values have already been computed. There are two ways to compute all values:
- Compute them directly according to the recurrence.
- Enumerate from large to small and perform the transitions. The increment is nonzero only when , so the recurrence can be optimized with suffix sums.
Now consider how to compute .
Looking at the process of computing , it is easy to see that has useful values only at the points , of which there are .
In general, is a low-degree polynomial in , which can be written as .
Thus, the contribution of each to is .
Considering the contribution of each separately, the problem becomes: given , for every , find .
Note
is a completely multiplicative function!
Now define , namely, after the -th round of the Eratosthenes sieve, the sum of the values of the numbers that remain.
For a composite number , we necessarily have . Let be the largest prime not exceeding . Then , meaning that after rounds of the Eratosthenes sieve, only primes remain.
Considering the boundary value of , it is clearly . (Recall that we specially defined .)
For the transition, consider the Eratosthenes sieve process and separate the contributions of each part:
- For the part with , the value of does not change, so .
- For the part with , every removed number has as a prime factor, contributing .
- In the second part, because , the condition is met by numbers . This part must be added back, giving .
Thus,
Complexity analysis
For computing , the time complexity of the first method has been proved to be (see Section 2.3 of Zhen-Ting Zhu's training-team paper 《Some Special Summation Problems for Number-Theoretic Functions》);
for the second method, its essence is the second part of the Du sieve, which is also mentioned in Ren Zhizhou's paper 《Several Methods for Summing Multiplicative Functions》 (Section 6.5.4), and its time complexity has been proved to be .
For computing , the implementation is in fact the same as the first part of the Du sieve.
For each , only enumerating a value satisfying and then processing the prime contributes to the time complexity, so the time complexity can be estimated as follows:
For the space complexity, observe that both and take effective values only at , for a total of points. Recording only the effective values reduces the space complexity to .
First, a single number-theoretic block decomposition gives all effective values; record them in an array of size called . For an effective value , let be the index of in . It is easy to see that for every effective value , .
Then consider separately effective values no greater than and those greater than : for effective values no greater than with value , use an array to record its , namely ; for effective values greater than with value , use an array to record . Since is too large, use to record , namely .
Thus, two arrays of size can record the of every effective value and support queries. When computing or , use the of an effective value as the index instead of the value itself, thereby reducing the space complexity to .
Procedure
For computing , in implementations we generally choose the first method because it is easier to implement and often performs better than the second method when the data size is small.
For computing , implement the recurrence directly.
For , a linear sieve can preprocess to replace in the recurrence for .
Correspondingly, in the recurrence for , the value can also be preprocessed in this way.
When using the Extended Eratosthenes Sieve to compute the prefix sum of a multiplicative function , the following points must be clear:
- How to quickly sieve out (generally in linear time) the first values of ;
- The polynomial representation of ;
- How to quickly compute .
After clarifying the points above, implement the following parts in order:
- Sieve the primes in and the first values of ;
- For every term in the polynomial representation of , sieve out the corresponding and combine them to obtain all useful values of numbered ;
- Implement the recurrence for recursively to obtain .
Examples
Luogu P4213 [Template] Du sieve
Find and .
Solution
To compute the prefix sum of , we first know that . For the linear term of , we have ; for the constant term of , we have . Sieve twice and add the results to obtain all values at the required points.
To compute the prefix sum of , we know that . Thus . A single sieve gives all values at the required points.
LOJ 6053 Simple Function
Given :
Find .
Solution
It is easy to see that . Thus, sieve it in the same way as , with a separate case for .
Reference code
/* "LOJ #6053" Simple Function */
#include <cmath>
#include <iostream>
constexpr int MAXS = 200000; // 2sqrt(n)
constexpr int mod = 1000000007;
template <typename x_t, typename y_t>
void inc(x_t &x, const y_t &y) {
x += y;
(mod <= x) && (x -= mod);
}
template <typename x_t, typename y_t>
void dec(x_t &x, const y_t &y) {
x -= y;
(x < 0) && (x += mod);
}
template <typename x_t, typename y_t>
int sum(const x_t &x, const y_t &y) {
return x + y < mod ? x + y : (x + y - mod);
}
template <typename x_t, typename y_t>
int sub(const x_t &x, const y_t &y) {
return x < y ? x - y + mod : (x - y);
}
template <typename _Tp>
int div2(const _Tp &x) {
return ((x & 1) ? x + mod : x) >> 1;
}
// all of the above are to avoid negative numbers and handle modular reduction
template <typename _Tp>
long long sqrll(const _Tp &x) { // square function
return (long long)x * x;
}
int pri[MAXS / 7], lpf[MAXS + 1], spri[MAXS + 1], pcnt;
void sieve(const int &n) {
for (int i = 2; i <= n; ++i) {
if (lpf[i] == 0) { // record the prime
lpf[i] = ++pcnt;
pri[lpf[i]] = i;
spri[pcnt] = sum(spri[pcnt - 1], i); // prefix sum
}
for (int j = 1, v; j <= lpf[i] && (v = i * pri[j]) <= n; ++j) lpf[v] = j;
}
}
long long global_n;
int lim;
int le[MAXS + 1], // x <= \sqrt{n}
ge[MAXS + 1]; // x > \sqrt{n}
#define idx(v) (v <= lim ? le[v] : ge[global_n / v])
int G[MAXS + 1][2], Fprime[MAXS + 1];
long long lis[MAXS + 1];
int cnt;
void init(const long long &n) {
for (long long i = 1, j, v; i <= n; i = n / j + 1) {
j = n / i;
v = j % mod;
lis[++cnt] = j;
(j <= lim ? le[j] : ge[global_n / j]) = cnt;
G[cnt][0] = sub(v, 1ll);
G[cnt][1] = div2((long long)(v + 2ll) * (v - 1ll) % mod);
}
}
void calcFprime() {
for (int k = 1; k <= pcnt; ++k) {
const int p = pri[k];
const long long sqrp = sqrll(p);
for (int i = 1; lis[i] >= sqrp; ++i) {
const long long v = lis[i] / p;
const int id = idx(v);
dec(G[i][0], sub(G[id][0], k - 1));
dec(G[i][1], (long long)p * sub(G[id][1], spri[k - 1]) % mod);
}
}
/* F_prime = G_1 - G_0 */
for (int i = 1; i <= cnt; ++i) Fprime[i] = sub(G[i][1], G[i][0]);
}
int f_p(const int &p, const int &c) {
/* f(p^{c}) = p xor c */
return p ^ c;
}
int F(const int &k, const long long &n) {
if (n < pri[k] || n <= 1) return 0;
const int id = idx(n);
long long ans = Fprime[id] - (spri[k - 1] - (k - 1));
if (k == 1) ans += 2;
for (int i = k; i <= pcnt && sqrll(pri[i]) <= n; ++i) {
long long pw = pri[i], pw2 = sqrll(pw);
for (int c = 1; pw2 <= n; ++c, pw = pw2, pw2 *= pri[i])
ans +=
((long long)f_p(pri[i], c) * F(i + 1, n / pw) + f_p(pri[i], c + 1)) %
mod;
}
return ans % mod;
}
using std::cin;
using std::cout;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> global_n;
lim = sqrt(global_n); // upper bound
sieve(lim + 1000); // preprocess
init(global_n);
calcFprime();
cout << (F(1, global_n) + 1ll + mod) % mod << '\n';
return 0;
}