Powerful Number Sieve

Definition

The Powerful Number (hereinafter PN) sieve is similar to the Du sieve, or more precisely an extension of it, and can be used to compute prefix sums of some multiplicative functions.

Requirement:

  • There exists a function gg satisfying:
    • gg is a multiplicative function.
    • gg has easily computed prefix sums.
    • For every prime pp, g(p)=f(p)g(p) = f(p).

Assume that we want the prefix sum of the multiplicative function ff, F(n)=∑i=1nf(i)F(n) = \sum_{i=1}^{n} f(i).

Powerful Number

Definition: For a positive integer nn, write the prime factorization of nn as n=∏i=1mpiein = \prod_{i=1}^{m} p_{i}^{e_{i}}. nn is a PN if and only if ∀1≤i≤m,ei>1\forall 1 \le i \le m, e_{i} > 1.

Property 1: Every PN can be represented in the form a2b3a^{2}b^{3}.

Proof: If eie_i is even, merge pieip_{i}^{e_{i}} into a2a^{2}; if eie_i is odd, first merge pi3p_{i}^{3} into b3b^{3}, and then merge piei−3p_{i}^{e_{i}-3} into a2a^{2}.

Property 2: There are at most O(n)O(\sqrt{n}) PNs no greater than nn.

Proof: Enumerate aa, then count the number of bb satisfying the condition. The number of PNs is approximately

∫1nnx23dx=O(n)\int_{1}^{\sqrt{n}} \sqrt[3]{\frac{n}{x^2}} \mathrm{d}x = O(\sqrt{n})

So how do we find all PNs no greater than nn? Use a linear sieve to find all primes no greater than n\sqrt{n}, then search the exponents of the primes with DFS. Since there are at most O(n)O(\sqrt{n}) PNs no greater than nn, we need search at most O(n)O(\sqrt{n}) times.

PN Sieve

First, construct a multiplicative function gg whose prefix sums are easy to compute and which satisfies g(p)=f(p)g(p) = f(p) for every prime pp. Let G(n)=∑i=1ng(i)G(n) = \sum_{i=1}^{n} g(i).

Next, construct h=f/gh = f / g, where // denotes Dirichlet-convolution division. From the properties of Dirichlet convolution, hh is also multiplicative, so h(1)=1h(1) = 1. We have f=g∗hf = g * h, where ∗* denotes Dirichlet convolution.

For a prime pp, f(p)=g(1)h(p)+g(p)h(1)=h(p)+g(p)  ⟹  h(p)=0f(p) = g(1)h(p) + g(p)h(1) = h(p) + g(p) \implies h(p) = 0. From h(p)=0h(p)=0 and the fact that hh is multiplicative, we can conclude that h(n)=0h(n) = 0 for every non-PN number nn; that is, hh is nonzero only at PNs.

Now, from f=g∗hf = g * h, we have

F(n)=∑i=1nf(i)=∑i=1n∑d∣ih(d)g(id)=∑d=1n∑i=1⌊nd⌋h(d)g(i)=∑d=1nh(d)∑i=1⌊nd⌋g(i)=∑d=1nh(d)G(⌊nd⌋)=∑d=1d is PNnh(d)G(⌊nd⌋)\begin{aligned} F(n) &= \sum_{i = 1}^{n} f(i)\\ &= \sum_{i = 1}^{n} \sum_{d|i} h(d) g\left(\frac{i}{d}\right)\\ &= \sum_{d=1}^{n} \sum_{i=1}^{\lfloor \frac{n}{d}\rfloor} h(d) g(i)\\ &= \sum_{d=1}^{n} h(d) \sum_{i=1}^{\lfloor \frac{n}{d}\rfloor} g(i) \\ &= \sum_{d=1}^{n} h(d) G\left(\left\lfloor \frac{n}{d}\right\rfloor\right)\\ &= \sum_{\substack{d=1 \\ d \text{ is PN}}}^{n}h(d) G\left(\left\lfloor \frac{n}{d}\right\rfloor\right) \end{aligned}

Find all PNs in O(n)O(\sqrt{n}) time and compute all effective values of hh. To compute all effective values of hh, it is enough to compute the values at all h(pc)h(p^c); the multiplicativity of hh then gives all the effective values of hh. For every effective value dd, compute h(d)G(⌊nd⌋)h(d)G\left(\left\lfloor \dfrac{n}{d} \right\rfloor\right) and add them to obtain F(n)F(n).

Now consider computing h(pc)h(p^c). There are two methods: one is to derive directly a formula for h(pc)h(p^c) that depends only on p,cp, c, and then compute h(pc)h(p^c) from the formula; the other uses f=g∗hf = g * h, which gives f(pc)=∑i=0cg(pi)h(pc−i)f(p^c) = \sum_{i=0}^c g(p^i)h(p^{c-i}). Rearranging yields h(pc)=f(pc)−∑i=1cg(pi)h(pc−i)h(p^c) = f(p^c) - \sum_{i=1}^{c}g(p^i)h(p^{c-i}), so we can enumerate primes pp and then exponents cc to solve for all h(pc)h(p^c).

Procedure

  1. Construct gg.
  2. Construct a method for quickly computing GG.
  3. Compute h(pc)h(p^c).
  4. Search for PNs, accumulating the answer along the way.
  5. Obtain the result.

For step 3, we can compute directly from a formula, use enumeration to precompute a table, or derive the value temporarily when it is reached during the search.

Properties

Take the second method for computing h(pc)h(p^c) as an example. The analysis can be divided into computing h(pc)h(p^c) and searching.

For the first part, the number of primes no greater than O(n)O(\sqrt{n}) is O(nlog⁡n)O\left(\dfrac{\sqrt{n}}{\log n}\right), and the exponent cc of each prime pp is at most log⁡n\log n. Computing h(pc)h(p^c) requires (c−1)(c - 1) iterations, so the time complexity of the first part is O(nlog⁡n⋅log⁡n⋅log⁡n)=O(nlog⁡n)O\left(\dfrac{\sqrt{n}}{\log n} \cdot \log n \cdot \log n\right) = O(\sqrt{n}\log{n}). This is a loose upper bound; depending on the problem, different optimizations can further reduce the time complexity of the first part.

For the search part, there are at most O(n)O(\sqrt{n}) PNs no greater than nn, so we search at most O(n)O(\sqrt{n}) times. For each PN, the time complexity also depends on how GG is computed. For example, if computing G(⌊nd⌋)G\left(\left\lfloor \dfrac{n}{d}\right\rfloor\right) takes O(1)O(1) time, then the second part has complexity O(n)O(\sqrt{n}).

In particular, if the Du sieve is used to compute G(⌊nd⌋)G\left(\left\lfloor \dfrac{n}{d}\right\rfloor\right), then the time complexity of the second part is that of the Du sieve, namely O(n23)O(n^{\frac{2}{3}}). If we compute G(n)G(n) once in advance, optimize with a linear sieve, and record larger values in a data structure supporting fast random access (such as std::map and std::unordered_map in C++), then every G(⌊nd⌋)G\left(\left\lfloor \dfrac{n}{d}\right\rfloor\right) used by the Du sieve is either recorded by the linear sieve or stored in std::map; this can be verified directly by a program.

For space complexity, the bottleneck is storing h(pc)h(p^c). If a two-dimensional array aa is used, where ai,ja_{i,j} stores the value of h(pij)h(p_i^j), then the space complexity is O(nlog⁡n⋅log⁡n)=O(n)O\left(\dfrac{\sqrt{n}}{\log n} \cdot \log n\right) = O(\sqrt{n}).

Examples

Luogu P5325 【Template】 Min_25 Sieve

Problem: Given the multiplicative function f(pk)=pk(pk−1)f(p^k) = p^k(p^k-1), find ∑i=1nf(i)\sum_{i=1}^{n} f(i).

It is easy to obtain f(p)=p(p−1)=id⁡(p)φ(p)f(p) = p(p-1) = \operatorname{id}(p)\varphi(p), so construct g(n)=id⁡(n)φ(n)g(n) = \operatorname{id}(n)\varphi(n).

Consider using the Du sieve to compute G(n)G(n). From (id⁡⋅φ)∗id⁡=id⁡2(\operatorname{id}\cdot \varphi) * \operatorname{id} = \operatorname{id}_2, we obtain G(n)=∑i=1ni2−∑d=2nd⋅G(⌊nd⌋)G(n)= \sum_{i=1}^{n} i^2 - \sum_{d=2}^{n} d \cdot G\left(\left\lfloor \dfrac{n}{d} \right\rfloor\right).

The values of h(pk)h(p^k) can then be computed by enumeration; this method is omitted.

In addition, this problem allows us to derive a formula for h(pk)h(p^k) that depends only on p,kp, k. The derivation is as follows:

f(pk)=∑i=0kg(pk−i)h(pi)  ⟺  pk(pk−1)=∑i=0kpk−iφ(pk−i)h(pi)  ⟺  pk(pk−1)=∑i=0kp2k−2i−1(p−1)h(pi)  ⟺  pk(pk−1)=h(pk)+∑i=0k−1p2k−2i−1(p−1)h(pi)  ⟺  h(pk)=pk(pk−1)−∑i=0k−1p2k−2i−1(p−1)h(pi)  ⟺  h(pk)−p2h(pk−1)=pk(pk−1)−pk+1(pk−1−1)−p(p−1)h(pk−1)  ⟺  h(pk)−ph(pk−1)=pk+1−pk  ⟺  h(pk)pk−h(pk−1)pk−1=p−1\begin{aligned} & f(p^k) = \sum_{i=0}^{k} g(p^{k-i})h(p^i)\\ \iff & p^k(p^k-1) = \sum_{i=0}^{k} p^{k-i}\varphi(p^{k-i}) h(p^i)\\ \iff & p^k(p^k-1) = \sum_{i=0}^{k} p^{2k-2i-1}(p - 1) h(p^i)\\ \iff & p^k(p^k-1) = h(p^k) + \sum_{i=0}^{k-1} p^{2k-2i-1}(p - 1) h(p^i)\\ \iff & h(p^k) = p^k(p^k-1) - \sum_{i=0}^{k-1} p^{2k-2i-1}(p - 1) h(p^i)\\ \iff & h(p^k) - p^2h(p^{k-1}) = p^{k}(p^k-1)-p^{k+1}(p^{k-1}-1) - p(p-1)h(p^{k-1})\\ \iff & h(p^k) - ph(p^{k-1}) = p^{k+1} - p^k\\ \iff & \frac{h(p^k)}{p^k} - \frac{h(p^{k-1})}{p^{k-1}} = p - 1\\ \end{aligned}

Using h(p)=0h(p) = 0 and summing, we obtain h(pk)=(k−1)(p−1)pkh(p^k) = (k-1)(p-1)p^k.

Reference code
#include <iostream>
#include <map>
using namespace std;

constexpr int MOD = 1e9 + 7;

template <typename T>
int mint(T x) {
  x %= MOD;
  if (x < 0) x += MOD;
  return x;
}

int add(int x, int y) { return x + y >= MOD ? x + y - MOD : x + y; }

int mul(int x, int y) { return (long long)1 * x * y % MOD; }

int sub(int x, int y) {
  return x < y ? x - y + MOD : x - y;  // prevents negative values
}

int qp(int x, int y) {
  int r = 1;
  for (; y; y >>= 1) {
    if (y & 1) r = mul(r, x);
    x = mul(x, x);
  }
  return r;
}

int inv(int x) { return qp(x, MOD - 2); }

namespace PNS {
constexpr int N = 2e6 + 5;
constexpr int M = 35;

long long global_n;

int g[N], sg[N];

int h[N][M];
bool vis_h[N][M];

int ans;

int pcnt, prime[N], phi[N];
bool isp[N];

void sieve(int n) {
  pcnt = 0;
  for (int i = 2; i <= n; ++i) isp[i] = true;  // mark the prime array
  phi[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (isp[i]) {
      ++pcnt;
      prime[pcnt] = i;
      phi[i] = i - 1;
    }
    for (int j = 1; j <= pcnt; ++j) {  // sieve out composite numbers
      long long nxt = (long long)1 * i * prime[j];
      if (nxt > n) break;
      isp[nxt] = false;
      if (i % prime[j] == 0) {  // the case where i is composite
        phi[nxt] = phi[i] * prime[j];
        break;
      }
      phi[nxt] = phi[i] * phi[prime[j]];
    }
  }

  for (int i = 1; i <= n; ++i) g[i] = mul(i, phi[i]);

  sg[0] = 0;
  for (int i = 1; i <= n; ++i) sg[i] = add(sg[i - 1], g[i]);  // prefix sum of g
}

int inv2, inv6;

void init() {
  sieve(N - 1);
  for (int i = 1; i <= pcnt; ++i) h[i][0] = 1, h[i][1] = 0;
  for (int i = 1; i <= pcnt; ++i) vis_h[i][0] = vis_h[i][1] = true;
  inv2 = inv(2);
  inv6 = inv(6);
}

int S1(long long n) { return mul(mul(mint(n), mint(n + 1)), inv2); }

int S2(long long n) {
  return mul(mul(mint(n), mul(mint(n + 1), mint(n * 2 + 1))), inv6);
}

map<long long, int> mp_g;

int G(long long n) {
  if (n < N) return sg[n];
  if (mp_g.count(n)) return mp_g[n];

  int ret = S2(n);
  for (long long i = 2, j; i <= n; i = j + 1) {
    j = n / (n / i);
    ret = sub(ret, mul(sub(S1(j), S1(i - 1)), G(n / i)));
  }
  mp_g[n] = ret;
  return ret;
}

void dfs(long long d, int hd, int pid) {
  ans = add(ans, mul(hd, G(global_n / d)));

  for (int i = pid; i <= pcnt; ++i) {
    if (i > 1 && d > global_n / prime[i] / prime[i]) break;  // pruning

    int c = 2;
    for (long long x = d * prime[i] * prime[i]; x <= global_n;
         x *= prime[i], ++c) {  // compute the f*g function
      if (!vis_h[i][c]) {
        int f = qp(prime[i], c);
        f = mul(f, sub(f, 1));
        int g = mul(prime[i], prime[i] - 1);
        int t = mul(prime[i], prime[i]);

        for (int j = 1; j <= c; ++j) {
          f = sub(f, mul(g, h[i][c - j]));
          g = mul(g, t);
        }
        h[i][c] = f;
        vis_h[i][c] = true;
      }

      if (h[i][c]) dfs(x, mul(hd, h[i][c]), i + 1);
    }
  }
}

int solve(long long n) {
  global_n = n;
  ans = 0;
  dfs(1, 1, 1);
  return ans;
}
}  // namespace PNS

int main() {
  PNS::init();
  long long n;
  cin >> n;
  cout << PNS::solve(n) << '\n';
  return 0;
}

「LOJ #6053」Simple Function

Given f(n)f(n):

f(n)={1n=1p⊕cn=pcf(a)f(b)n=ab and a⊥bf(n) = \begin{cases} 1 & n = 1 \\ p \oplus c & n=p^c \\ f(a)f(b) & n=ab \text{ and } a \perp b \end{cases}

It is easy to obtain:

f(p)={p+1p=2p−1otherwisef(p) = \begin{cases} p + 1 & p = 2 \\ p - 1 & \text{otherwise} \\ \end{cases}

Construct gg as

g(n)={3φ(n)2∣nφ(n)otherwiseg(n) = \begin{cases} 3 \varphi(n) & 2 \mid n \\ \varphi(n) & \text{otherwise} \\ \end{cases}

It is easy to prove that g(p)=f(p)g(p) = f(p) and gg is multiplicative.

Now consider computing G(n)G(n).

G(n)=∑i=1n[i mod 2=1]φ(i)+3∑i=1n[i mod 2=0]φ(i)=∑i=1nφ(i)+2∑i=1n[i mod 2=0]φ(i)=∑i=1nφ(i)+2∑i=1⌊n2⌋φ(2i)\begin{aligned} G(n) &= \sum_{i=1}^{n}[i \bmod 2 = 1] \varphi(i) + 3 \sum_{i=1}^{n}[i \bmod 2 = 0] \varphi(i)\\ &= \sum_{i=1}^{n} \varphi(i) + 2\sum_{i=1}^{n} [i \bmod 2 = 0]\varphi(i) \\ &= \sum_{i=1}^{n} \varphi(i) + 2\sum_{i=1}^{\lfloor \frac{n}{2} \rfloor} \varphi(2i) \end{aligned}

Let S1(n)=∑i=1nφ(i)S_1(n) = \sum_{i=1}^{n} \varphi(i) and S2(n)=∑i=1nφ(2i)S_2(n) = \sum_{i=1}^{n} \varphi(2i). Then G(n)=S1(n)+2S2(⌊n2⌋)G(n) = S_1(n) + 2S_2\left(\left\lfloor \dfrac{n}{2} \right\rfloor\right).

When 2∣n2 \mid n, we have

S2(n)=∑i=1nφ(2i)=∑i=1n2(φ(2(2i−1))+φ(2(2i)))=∑i=1n2(φ(2i−1)+2φ(2i))=∑i=1n2(φ(2i−1)+φ(2i))+∑i=1n2φ(2i)=∑i=1nφ(i)+S2(n2)=S1(n)+S2(⌊n2⌋)\begin{aligned} S_2(n) &= \sum_{i=1}^{n} \varphi(2i) \\ &= \sum_{i=1}^{\frac{n}{2}} (\varphi(2(2i-1)) + \varphi(2(2i))) \\ &= \sum_{i=1}^{\frac{n}{2}} (\varphi(2i-1) + 2\varphi(2i)) \\ &= \sum_{i=1}^{\frac{n}{2}} (\varphi(2i-1) + \varphi(2i)) + \sum_{i=1}^{\frac{n}{2}} \varphi(2i) \\ &= \sum_{i=1}^{n} \varphi(i) + S_2\left(\frac{n}{2}\right)\\ &= S_1(n) + S_2\left(\left\lfloor \frac{n}{2} \right\rfloor\right)\\ \end{aligned}

When 2∤n2 \nmid n, we have

S2(n)=S2(n−1)+φ(2n)=S2(n−1)+φ(n)=∑i=1n−1φ(i)+S2(n−12)+φ(n)=S1(n)+S2(⌊n2⌋)\begin{aligned} S_2(n) &= S_2(n-1) + \varphi(2n) \\ &= S_2(n-1) + \varphi(n) \\ &= \sum_{i=1}^{n-1} \varphi(i) + S_2\left(\frac{n-1}{2}\right) + \varphi(n)\\ &= S_1(n) + S_2\left(\left\lfloor \frac{n}{2} \right\rfloor\right)\\ \end{aligned}

Therefore, S2(n)=S1(n)+S2(⌊n2⌋)S_2(n) = S_1(n) + S_2\left(\left\lfloor \dfrac{n}{2} \right\rfloor\right).

S1S_1 can be computed with the Du sieve, while S2S_2 follows directly from the formula, so GG can also be computed.

Reference code
#include <iostream>
#include <map>
using namespace std;

constexpr int MOD = 1e9 + 7;
constexpr int inv2 = (MOD + 1) / 2;

template <typename T>
int mint(T x) {
  x %= MOD;
  if (x < 0) x += MOD;
  return x;
}

int add(int x, int y) {
  return x + y >= MOD ? x + y - MOD : x + y;  // prevents values from exceeding the modulus
}

int mul(int x, int y) { return (long long)1 * x * y % MOD; }

int sub(int x, int y) {
  return x < y ? x - y + MOD : x - y;  // prevents negative values
}

namespace PNS {
constexpr int N = 2e6 + 5;
constexpr int M = 35;

long long global_n;

int s1[N], s2[N];

int h[N][M];
bool vis_h[N][M];

int ans;

int pcnt, prime[N], phi[N];
bool isp[N];

void sieve(int n) {
  pcnt = 0;
  for (int i = 2; i <= n; ++i) isp[i] = true;  // mark the prime array
  phi[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (isp[i]) {
      ++pcnt;
      prime[pcnt] = i;
      phi[i] = i - 1;
    }
    for (int j = 1; j <= pcnt; ++j) {  // sieve out composite numbers
      long long nxt = (long long)1 * i * prime[j];
      if (nxt > n) break;
      isp[nxt] = false;
      if (i % prime[j] == 0) {  // the case where i is composite
        phi[nxt] = phi[i] * prime[j];
        break;
      }
      phi[nxt] = phi[i] * phi[prime[j]];
    }
  }

  s1[0] = 0;
  for (int i = 1; i <= n; ++i) s1[i] = add(s1[i - 1], phi[i]);

  s2[0] = 0;
  for (int i = 1; i <= n / 2; ++i) {
    s2[i] = add(s2[i - 1], phi[2 * i]);
  }
}

void init() {
  sieve(N - 1);
  for (int i = 1; i <= pcnt; ++i) h[i][0] = 1;
  for (int i = 1; i <= pcnt; ++i) vis_h[i][0] = true;
}

map<long long, int> mp_s1;

int S1(long long n) {
  if (n < N) return s1[n];
  if (mp_s1.count(n)) return mp_s1[n];

  int ret = mul(mul(mint(n), mint(n + 1)), inv2);
  for (long long i = 2, j; i <= n; i = j + 1) {
    j = n / (n / i);
    ret = sub(ret, mul(mint(j - i + 1), S1(n / i)));
  }
  mp_s1[n] = ret;
  return ret;
}

map<long long, int> mp_s2;

int S2(long long n) {
  if (n < N / 2) return s2[n];
  if (mp_s2.count(n)) return mp_s2[n];
  int ret = add(S1(n), S2(n / 2));
  mp_s2[n] = ret;
  return ret;
}

int G(long long n) { return add(S1(n), mul(2, S2(n / 2))); }

void dfs(long long d, int hd, int pid) {
  ans = add(ans, mul(hd, G(global_n / d)));

  for (int i = pid; i <= pcnt; ++i) {
    if (i > 1 && d > global_n / prime[i] / prime[i]) break;  // pruning

    int c = 2;
    for (long long x = d * prime[i] * prime[i]; x <= global_n;
         x *= prime[i], ++c) {
      if (!vis_h[i][c]) {
        int f = prime[i] ^ c, g = prime[i] - 1;

        // handle p = 2 as a special case
        if (i == 1) g = mul(g, 3);

        for (int j = 1; j <= c; ++j) {
          f = sub(f, mul(g, h[i][c - j]));
          g = mul(g, prime[i]);
        }
        h[i][c] = f;
        vis_h[i][c] = true;
      }

      if (h[i][c]) dfs(x, mul(hd, h[i][c]), i + 1);
    }
  }
}

int solve(long long n) {
  global_n = n;
  ans = 0;
  dfs(1, 1, 1);
  return ans;
}
}  // namespace PNS

int main() {
  PNS::init();  // preprocessing function
  long long n;
  cin >> n;
  cout << PNS::solve(n) << '\n';
  return 0;
}

Exercises

References

This content comes from OI Wiki: Powerful Number Sieve. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.