前置知识:数论分块狄利克雷卷积

莫比乌斯反演是数论中的重要内容.对于一些函数 ,如果很难直接求出它的值,而容易求出其倍数和或约数和 ,那么可以通过莫比乌斯反演简化运算,求得 的值.

莫比乌斯函数

莫比乌斯函数(Möbius 函数)定义为

具体地,假设正整数 有素因数分解 ,其中, 是素数, 是正整数.那么,三种情形分别对应:

  1. 当存在 使得 ,即存在任何素因数出现超过一次时,
  2. 否则,对于所有 都有 ,即任何素因数都只出现一次时,,其中, 就是互异素因子的个数.

性质

根据定义容易验证,莫比乌斯函数 是积性函数,但不是完全积性函数.除此之外,最为重要的性质是下述恒等式:

性质

对于正整数 ,有

其中 是 Iverson 括号.

利用 Dirichlet 卷积,该表达式可以写作 .也就是说,莫比乌斯函数是常值函数 的 Dirichlet 逆.

这一性质有一个很常见的应用:

它将互素的条件转化为关于莫比乌斯函数的求和式,方便进一步推导.

求法

如果需要对单个 计算莫比乌斯函数 的值,可以利用它的 质因数分解.例如,在 不太大时,可以在 时间内求出 的值.

参考实现

[list2tab]

  • C++

    int mu(int n) {
      int res = 1;
      for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
          n /= i;
          // Check if square-free.
          if (n % i == 0) return 0;
          res = -res;
        }
      }
      // The remaining factor must be prime.
      if (n > 1) res = -res;
      return res;
    }
  • Python

    def mu(n):
        res = 1
        i = 2
        while i * i <= n:
            if n % i == 0:
                n //= i
                # Check if square-free
                if n % i == 0:
                    return 0
                res = -res
            i += 1
        # The remaining factor must be prime
        if n > 1:
            res = -res
        return res

如果需要对前 个正整数预处理出 的值,可以利用它是积性函数,通过 线性筛 时间内计算.

参考实现

[list2tab]

  • C++

    std::vector<int> get_mu(int n) {
      std::vector<int> mu(n + 1), primes;
      std::vector<bool> not_prime(n + 1);
      primes.reserve(n);
      mu[1] = 1;
      for (int x = 2; x <= n; ++x) {
        if (!not_prime[x]) {
          primes.push_back(x);
          mu[x] = -1;
        }
        for (int p : primes) {
          if (x * p > n) break;
          not_prime[x * p] = true;
          if (x % p == 0) {
            mu[x * p] = 0;
            break;
          } else {
            mu[x * p] = -mu[x];
          }
        }
      }
      return mu;
    }
  • Python

    def get_mu(n):
        mu = [0] * (n + 1)
        primes = []
        not_prime = [False] * (n + 1)
     
        mu[1] = 1
        for x in range(2, n + 1):
            if not not_prime[x]:
                primes.append(x)
                mu[x] = -1
            for p in primes:
                if x * p > n:
                    break
                not_prime[x * p] = True
                if x % p == 0:
                    mu[x * p] = 0
                    break
                else:
                    mu[x * p] = -mu[x]
        return mu

莫比乌斯反演

莫比乌斯函数最重要的应用就是莫比乌斯反演.

莫比乌斯反演

是两个数论函数.那么,有

在涉及各种整除关系的数论函数求和中,莫比乌斯反演是有力的变形工具.

例子

  1. 欧拉函数 满足关系式 ,亦即 .对它进行反演,就得到 ,亦即

  2. 除数函数 ,亦即 .对它进行反演,就得到 ,亦即

  3. 互异素因子数目函数 ,亦即 ,其中 是素数集 的指示函数.对它进行反演,就得到 ,亦即

  4. 考察满足 的数论函数 .它就是对数函数的莫比乌斯反演,也称为 von Mangoldt 函数:

拓展形式

除了上述基本形式外,莫比乌斯反演还有一些常见的拓展形式.首先,可以考虑它的倍数和形式.

拓展一

是两个数论函数.那么,有

其次,莫比乌斯反演并不仅限于加法,它实际上对于任何 Abel 群 中的运算都成立.例如,它有如下的乘法形式:

拓展二

是两个数论函数.那么,有

从 Dirichlet 卷积的角度看,莫比乌斯反演只是利用了「莫比乌斯函数是常值函数的 Dirichlet 逆」这一点.容易想象,类似莫比乌斯反演的关系对于一般的 Dirichlet 逆 同样成立.

拓展三

都是数论函数,且 的 Dirichlet 逆,即

那么,有

推论

是数论函数,且 是完全积性函数.那么,有

最后,莫比乌斯反演还可以推广到 上的复值函数,而不仅仅局限于数论函数.基本形式的莫比乌斯反演可以看作是复值函数在所有非整数点处均取零值的特殊情形.

拓展四

都是 上的复值函数.那么,有

推论

是数论函数.那么,有

这些拓展形式之间可以互相组合,进而得到更为复杂的反演关系.

Dirichlet 前缀和

前置知识:[前缀和与差分](https://leetcode.com/problems/前缀和 & 差分/)

考虑基本形式的莫比乌斯反演关系:

左侧等式中, 的值是 的所有因数处 的值之和.如果将 理解为 排在 之前,那么 就可以理解为某种意义下 的前缀和.因此,在国内竞赛圈,由 求出 的过程也称为 Dirichlet 前缀和,相应的逆过程则称为 Dirichlet 差分.这些方法大多出现在需要预处理某个数论函数在前 个点处取值的情形.

接下来,讨论 Dirichlet 前缀和的计算.如果将每一个素数都看作一个维度,这就是一种高维前缀和.回忆高维前缀和的 [逐维前缀和算法](https://leetcode.com/problems/前缀和 & 差分#逐维前缀和/):逐个遍历所有的维度,并将每个位置的值都累加到该位置在该维度上的后继位置.对于数论函数,这相当于说,从小到大遍历所有素数 ,并将 处的函数值累加到 处.这和 Eratosthenes 筛法 的遍历顺序是一致的.因此,这一算法可以在 时间内计算出长度为 的数列的 Dirichlet 前缀和.类似地,利用逐维差分就可以在相同时间复杂度内求出数列的 Dirichlet 差分.

参考实现

[list2tab]

  • Dirichlet 前缀和

    std::vector<int> dirichlet_presum(const std::vector<int>& g) {
      int n = g.size() - 1;
      std::vector<int> f(g);
      std::vector<bool> vis(n + 1);
      for (int x = 2; x <= n; ++x) {
        if (vis[x]) continue;
        for (int y = 1, xy = x; xy <= n; ++y, xy += x) {
          vis[xy] = true;
          f[xy] += f[y];
        }
      }
      return f;
    }
  • Dirichlet 差分

    std::vector<int> dirichlet_diff(const std::vector<int>& f) {
      int n = f.size() - 1;
      std::vector<int> g(f);
      std::vector<int> vis(n + 1);
      for (int x = 2; x <= n; ++x) {
        if (vis[x]) continue;
        for (int y = n / x, xy = x * y; y; --y, xy -= x) {
          vis[xy] = true;
          g[xy] -= g[y];
        }
      }
      return g;
    }

这一计算方法可以推广到倍数和(拓展一)、乘积形式(拓展二)、利用完全积性函数代替常值函数(拓展三的推论)等拓展形式中.

例题

本节通过例题展示莫比乌斯反演的应用方法以及一些常见变形技巧.首先,通过一道例题熟悉处理求和式中最大公因数条件的基本技巧.

组数据.对每组数据,求值:

数据范围:

接下来的两道例题展示了枚举公因数的处理方法,并利用 筛法 计算一般积性函数的值.

组数据.对每组数据,求值:

数据范围:

求值:

数据范围:

接下来的一道例题较为特殊,需要对乘积的约数个数函数进行转换.

组数据.对每组数据,求值:

其中, 表示 的约数个数.

数据范围:

最后一道例题展示了如何应用乘法版本的莫比乌斯反演.

求值:

数据范围:

习题

参考文献