【学习笔记】Min25筛

【定理简介】

  • M i n 25 筛是一种能够求解积性函数 f ( x ) 的前缀和 i = 1 N f ( i ) 的筛法,其前提条件为 i = 1 N [ i   i s   a   p r i m e ] f ( i ) 可以由 i = 1 x [ i   i s   a   p r i m e ] i k 简单表示,或能够直接快速计算,其中 k 为一个较小的常数。
  • 其时间复杂度为 O ( N 3 4 L o g N ) ,空间复杂度为 O ( N )
  • 在求解过程中,我们还可以顺带求出对于每一个 x = N i i = 1 x [ i   i s   a   p r i m e ] i k 的值。在一些题目中,上述功能可以直接帮助我们解决问题。

【算法流程】

  • 首先,我们先来解决对于每一个 x = N i ,求解 i = 1 x [ i   i s   a   p r i m e ] i k 的值。

  • 我们先通过线性筛得到 N 以内所有的质数 p r i m e i 以及它们的前缀 k 次方和 s u m i = j = 1 i p r i m e j k

  • 定义 g ( N , i ) = j = 1 N [ j   i s   a   p r i m e   o r   M i n j > p r i m e i ] j k ,其中 M i n i 表示 i 最小的质因数。

  • 直观地来说, g ( N , i ) 表示的就是 N 以内的在埃拉特斯特尼筛算法进行第 i 轮后尚未被筛去的数的 k 次方和。

  • 一个合数 X 一定存在一个 X 以内的质因数,因此 g ( N , C n t ) 即为所求,其中 C n t N 以内的质数个数。

  • 考虑如何通过 g ( , i 1 ) 求出 g ( , i )

  • p r i m e i 2 > N ,那么埃拉特斯特尼筛算法的第 i 轮将不会筛去任何数,有 g ( N , i ) = g ( N , i 1 )

  • p r i m e i 2 N ,考虑埃拉特斯特尼筛算法的第 i 轮筛去的数从 g ( N , i 1 ) 中删除,有

    g ( N , i ) = g ( N , i 1 ) p r i m e i k ( g ( N p r i m e i , i 1 ) s u m i 1 )

    这里由于 p r i m e i 2 N ,有 N p r i m e i p r i m e i ,因此 N p r i m e i 以内最小质因数大于等于 p r i m e i 的数的 k 次方之和即为 g ( N p r i m e i , i ) s u m i 1

  • 总结起来即为

    g ( N , i ) = { g ( N , i 1 ) p r i m e i 2 > N g ( N , i 1 ) p r i m e i k ( g ( N p r i m e i , i 1 ) s u m i 1 ) p r i m e i 2 N

  • 这部分的时间复杂度为 O ( N 3 4 L o g N )

  • 接下来,我们考虑如何用上述信息求解 i = 1 N f ( i )

  • 定义 s ( N , i ) = j = 1 N [ M i n j p r i m e i ] f ( j ) ,即所有满足最小质因子大于等于 p r i m e i f 值之和。

  • 由定义,最终答案 i = 1 N f ( i ) = g ( N , 1 ) + f ( 1 )

  • 经过上面的计算,我们已经可以快速计算 i = 1 N [ i   i s   a   p r i m e ] f ( i ) ,因此答案中质数的贡献能够被轻松计算: j = 1 N [ j   i s   a   p r i m e ] f ( j ) j = 1 i 1 f ( p r i m e j )

  • 接下来考虑答案中合数的贡献,我们枚举这个合数的最小质因子及其出现次数,由于 f 为积性函数,我们可以得到合数的贡献为 j = i p r i m e j 2 N k = 1 p r i m e j k + 1 N ( s ( N p r i m e j k , j + 1 ) f ( p r i m e j k ) + f ( p r i m e j k + 1 ) )

  • 总结起来即为

    s ( N , i ) = { 0 p r i m e i > N j = 1 N [ j   i s   a   p r i m e ] f ( j ) j = 1 i 1 f ( p r i m e j ) + j = i p r i m e j 2 N k = 1 p r i m e j k + 1 N ( s ( N p r i m e j k , j + 1 ) f ( p r i m e j k ) + f ( p r i m e j k + 1 ) ) p r i m e i N

  • 这部分计算即使不进行记忆化,也十分迅速,其复杂度被证明同样为 O ( N 3 4 L o g N )

【代码】

  • 模板题【LOJ6053】

  • 有了上面的推导过程,我们只需要能够快速计算 j = 1 N [ j   i s   a   p r i m e ] f ( j ) j = 1 i 1 f ( p r i m e j ) 即可解决问题。

  • 我们发现,对于质数 p

    f ( p ) = p   x o r   1 = { p + 1 p = 2 p 1 p 2

    因此当 i 1 ,即 p r i m e i > 2 时,

    j = 1 N [ j   i s   a   p r i m e ] f ( j ) j = 1 i 1 f ( p r i m e j ) 实际上计算了 [ p r i m e i , N ] 内质数的和减去质数的个数,可以通过上述方式预处理得到。

    i = 1 ,即 p r i m e i = 2 2 + 1 被当做 2 1 计算,因此加上 2 即可。

  • 时间复杂度 O ( N 3 4 L o g N )


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
const int inv2 = 5e8 + 4;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
long long n, limit, m, val[MAXN];
int tot, prime[MAXN], Min[MAXN], pre[MAXN];
int cnt[MAXN], sum[MAXN], home1[MAXN], home2[MAXN];
int s(long long x, int y) {
  if (x <= 1 || prime[y] > x) return 0;
  int pos = (x <= limit) ? home1[x] : home2[n / x];
  int ans = (sum[pos] - cnt[pos] + P) % P;
  ans = (ans - (pre[y - 1] - y + 1 + P) % P + P) % P;
  if (y == 1) ans = (ans + 2) % P;
  for (int i = y; i <= tot && 1ll * prime[i] * prime[i] <= x; i++) {
      long long now = prime[i], nxt = 1ll * prime[i] * prime[i];
      for (int j = 1; nxt <= x; j++, now *= prime[i], nxt *= prime[i])
          ans = (ans + 1ll * s(x / now, i + 1) * (prime[i] ^ j) + (prime[i] ^ (j + 1))) % P;
  }
  return ans;
}
void init(int n) {
  for (int i = 2; i <= n; i++) {
      if (Min[i] == 0) {
          Min[i] = i;
          prime[++tot] = i;
          pre[tot] = (pre[tot - 1] + i) % P;
      }
      for (int j = 1; j <= tot && prime[j] <= Min[i]; j++) {
          int tmp = prime[j] * i;
          if (tmp > n) break;
          Min[tmp] = prime[j];
      }
  }
}
int main() {
  read(n), limit = sqrt(n);
  init(limit);
  for (long long i = 1, nxt; i <= n; i = nxt) {
      long long tmp = n / i;
      nxt = n / tmp + 1;
      val[++m] = tmp;
      cnt[m] = (val[m] - 1) % P;
      sum[m] = (val[m] + 2) % P * ((val[m] - 1) % P) % P * inv2 % P;
      if (sum[m] < 0) sum[m] += P;
      if (tmp <= limit) home1[tmp] = m;
      else home2[i] = m;
  }
  for (int j = 1; j <= tot; j++)
  for (int i = 1; 1ll * prime[j] * prime[j] <= val[i]; i++) {
      long long tmp = val[i] / prime[j];
      if (tmp <= limit) cnt[i] -= cnt[home1[tmp]] - (j - 1);
      else cnt[i] -= cnt[home2[n / tmp]] - (j - 1);
      cnt[i] = (cnt[i] % P + P) % P;
      if (tmp <= limit) sum[i] -= 1ll * prime[j] * (sum[home1[tmp]] - pre[j - 1]) % P;
      else sum[i] -= 1ll * prime[j] * (sum[home2[n / tmp]] - pre[j - 1]) % P;
      sum[i] = (sum[i] % P + P) % P;
  }
  writeln((1 + s(n, 1)) % P);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/81543972
今日推荐