跳转至

莫比乌斯反演

引入

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

开始学习莫比乌斯反演前,需要先学习一些前置知识:数论分块狄利克雷卷积

莫比乌斯函数

定义

μ 为莫比乌斯函数,定义为

μ(n)={1n=10n 含有平方因子(1)kk 为 n 的本质不同质因子个数

详细解释一下:

n=i=1kpici,其中 pi 为质因子,ci1。上述定义表示:

  1. n=1 时,μ(n)=1
  2. 对于 n1 时:
    1. 当存在 i[1,k],使得 ci>1 时,μ(n)=0,也就是说只要某个质因子出现的次数超过一次,μ(n) 就等于 0
    2. 当任意 i[1,k],都有 ci=1 时,μ(n)=(1)k,也就是说每个质因子都仅仅只出现过一次时,即 n=i=1kpi{pi}i=1k 中个元素唯一时,μ(n) 等于 1k 次幂,此处 k 指的便是仅仅只出现过一次的质因子的总个数。

性质

莫比乌斯函数不仅是积性函数,还有如下性质:

dnμ(d)={1n=10n1

dnμ(d)=ε(n)μ1=ε

证明

n=i=1kpici,n=i=1kpi

那么 dnμ(d)=dnμ(d)=i=0k(ki)(1)i=(1+(1))k

根据二项式定理,易知该式子的值在 k=0n=1 时值为 1 否则为 0,这也同时证明了 dnμ(d)=[n=1]=ε(n) 以及 μ1=ε

这个性质意味着,莫比乌斯函数在狄利克雷生成函数中,等价于黎曼函数 ζ 的倒数。所以在狄利克雷卷积中,莫比乌斯函数是常数函数 1 的逆元。

补充结论

反演结论:[gcd(i,j)=1]=dgcd(i,j)μ(d)

直接推导:如果看懂了上一个结论,这个结论稍加思考便可以推出:如果 gcd(i,j)=1 的话,那么代表着我们按上个结论中枚举的那个 n1,也就是式子的值是 1,反之,有一个与 [gcd(i,j)=1] 相同的值:0

利用 ε 函数:根据上一结论,[gcd(i,j)=1]=ε(gcd(i,j)),将 ε 展开即可。

线性筛

由于 μ 函数为积性函数,因此可以线性筛莫比乌斯函数(线性筛基本可以求所有的积性函数,尽管方法不尽相同)。

线性筛实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void getMu() {
  mu[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!flg[i]) p[++tot] = i, mu[i] = -1;
    for (int j = 1; j <= tot && i * p[j] <= n; ++j) {
      flg[i * p[j]] = 1;
      if (i % p[j] == 0) {
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = -mu[i];
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def getMu():
mu[1] = 1
for i in range(2, n + 1):
    if flg[i] != 0:
        p[tot] = i; tot = tot + 1; mu[i] = -1
    j = 1
    while j <= tot and i * p[j] <= n:
        flg[i * p[j]] = 1
        if i % p[j] == 0:
            mu[i * p[j]] = 0
            break
        mu[i * p[j]] = mu[i * p[j]] - mu[i]
        j = j + 1

拓展

证明

φ1=id

n 分解质因数:n=i=1kpici

首先,因为 φ 是积性函数,故只要证明当 n=pcφ1=dnφ(nd)=id 成立即可。

因为 p 是质数,于是 d=p0,p1,p2,,pc

易知如下过程:

φ1=dnφ(nd)=i=0cφ(pi)=1+p0(p1)+p1(p1)++pc1(p1)=pc=id

该式子两侧同时卷 μ 可得 φ(n)=dndμ(nd)


莫比乌斯变换

f(n),g(n) 为两个数论函数。

形式一:如果有 f(n)=dng(d),那么有 g(n)=dnμ(d)f(nd)

这种形式下,数论函数 f(n) 称为数论函数 g(n) 的莫比乌斯变换,数论函数 g(n) 称为数论函数 f(n) 的莫比乌斯逆变换(反演)。

容易看出,数论函数 g(n) 的莫比乌斯变换,就是将数论函数 g(n) 与常数函数 1 进行狄利克雷卷积。

根据狄利克雷卷积与狄利克雷生成函数的对应关系,数论函数 g(n) 的莫比乌斯变换对应的狄利克雷生成函数,就是数论函数 g(n) 的狄利克雷生成函数与黎曼函数 ζ 的乘积。

形式二:如果有 f(n)=n|dg(d),那么有 g(n)=n|dμ(dn)f(d)

证明

方法一:对原式做数论变换。

dnμ(d)f(nd)=dnμ(d)kndg(k)=kng(k)dnkμ(d)=g(n)

dng(d) 来替换 f(nd),再变换求和顺序。最后一步变换的依据:dnμ(d)=[n=1],因此在 nk=1 时第二个和式的值才为 1。此时 n=k,故原式等价于 kn[n=k]g(k)=g(n)

方法二:运用卷积。

原问题为:已知 f=g1,证明 g=fμ

易知如下转化:fμ=g1μfμ=g(其中 1μ=ε)。

对于第二种形式:

类似上面的方法一,我们考虑逆推这个式子。

n|dμ(dn)f(d)=k=1+μ(k)f(kn)=k=1+μ(k)kn|dg(d)=n|dg(d)k|dnμ(k)=n|dg(d)ϵ(dn)=g(n)

我们把 d 表示为 kn 的形式,然后把 f 的原定义代入式子。

发现枚举 k 再枚举 kn 的倍数可以转换为直接枚举 n 的倍数再求出 k,发现后面那一块其实就是 ϵ, 整个式子只有在 d=n 的时候才能取到值。


问题形式

「HAOI 2011」Problem b

求值(多组数据)

i=xnj=ym[gcd(i,j)=k](1T,x,y,n,m,k5×104)

根据容斥原理,原式可以分成 4 块来处理,每一块的式子都为

i=1nj=1m[gcd(i,j)=k]

考虑化简该式子

i=1nkj=1mk[gcd(i,j)=1]

因为 gcd(i,j)=1 时对答案才用贡献,于是我们可以将其替换为 ε(gcd(i,j))ε(n) 当且仅当 n=1 时值为 1 否则为 0),故原式化为

i=1nkj=1mkε(gcd(i,j))

ε 函数展开得到

i=1nkj=1mkdgcd(i,j)μ(d)

变换求和顺序,先枚举 dgcd(i,j) 可得

d=1μ(d)i=1nk[di]j=1mk[dj]

易知 1nkd 的倍数有 nkd 个,故原式化为

d=1min(nk,mk)μ(d)nkdmkd

很显然,式子可以数论分块求解。

时间复杂度 Θ(N+Tn)

代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <algorithm>
#include <iostream>
using namespace std;
constexpr int N = 50000;
int mu[N + 5], p[N + 5];
bool flg[N + 5];

void init() {
  int tot = 0;
  mu[1] = 1;
  for (int i = 2; i <= N; ++i) {
    if (!flg[i]) {
      p[++tot] = i;
      mu[i] = -1;
    }
    for (int j = 1; j <= tot && i * p[j] <= N; ++j) {
      flg[i * p[j]] = true;
      if (i % p[j] == 0) {
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = -mu[i];
    }
  }
  for (int i = 1; i <= N; ++i) mu[i] += mu[i - 1];
}

int solve(int n, int m) {
  int res = 0;
  for (int i = 1, j; i <= min(n, m); i = j + 1) {
    j = min(n / (n / i), m / (m / i));
    res += (mu[j] - mu[i - 1]) * (n / i) * (m / i);  // 代推出来的式子
  }
  return res;
}

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  int T, a, b, c, d, k;
  init();  // 预处理mu数组
  cin >> T;
  for (int i = 1; i <= T; i++) {
    cin >> a >> b >> c >> d >> k;
    // 根据容斥原理,1<=x<=b&&1<=y<=d范围中的答案数减去1<=x<=b&&1<=y<=c-1范围中的答案数和
    //   1<=x<=a-1&&1<=y<=d范围中的答案数再加上1<=x<=a-1&&1<=y<=c-1范围中的答案数
    //   即可得到a<=x<=b&&c<=y<=d范围中的答案数
    // 这一步如果不懂可以画坐标图进行理解
    cout << solve(b / k, d / k) - solve(b / k, (c - 1) / k) -
                solve((a - 1) / k, d / k) + solve((a - 1) / k, (c - 1) / k)
         << '\n';
  }
  return 0;
}

「SPOJ 5971」LCMSUM

求值(多组数据)

i=1nlcm(i,n)s.t. 1T3×105,1n106

易得原式即

i=1ningcd(i,n)

将原式复制一份并且颠倒顺序,然后将 n 一项单独提出,可得

12(i=1n1ingcd(i,n)+i=n11ingcd(i,n))+n

根据 gcd(i,n)=gcd(ni,n),可将原式化为

12(i=1n1ingcd(i,n)+i=n11ingcd(ni,n))+n

两个求和式中分母相同的项可以合并。

12i=1n1n2gcd(i,n)+n

12i=1nn2gcd(i,n)+n2

可以将相同的 gcd(i,n) 合并在一起计算,故只需要统计 gcd(i,n)=d 的个数。当 gcd(i,n)=d 时,gcd(id,nd)=1,所以 gcd(i,n)=d 的个数有 φ(nd) 个。

故答案为

12dnn2φ(nd)d+n2

变换求和顺序,设 d=nd,合并公因式,式子化为

12n(dndφ(d)+1)

g(n)=dndφ(d),已知 g 为积性函数,于是可以 Θ(n) 筛出。每次询问 Θ(1) 计算即可。

下面给出这个函数筛法的推导过程:

首先考虑 g(pjk) 的值,显然它的约数只有 pj0,pj1,,pjk,因此

g(pjk)=w=0kpjwφ(pjw)

又有 φ(pjw)=pjw1(pj1),则原式可化为

w=0kpj2w1(pj1)

于是有

g(pjk+1)=g(pjk)+pj2k+1(pj1)

那么,对于线性筛中的 g(ipj)(pj|i),令 i=apjw(gcd(a,pj)=1),可得

g(ipj)=g(a)g(pjw+1)g(i)=g(a)g(pjw)

g(ipj)g(i)=g(a)pj2w+1(pj1)

同理有

g(i)g(ipj)=g(a)pj2w1(pj1)

因此

g(ipj)=g(i)+(g(i)g(ipj))pj2

时间复杂度Θ(n+T)

代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
constexpr int N = 1000000;
int tot, p[N + 5];
long long g[N + 5];
bool flg[N + 5];  // 标记数组

void solve() {
  g[1] = 1;
  for (int i = 2; i <= N; ++i) {
    if (!flg[i]) {
      p[++tot] = i;
      g[i] = (long long)1 * i * (i - 1) + 1;
    }
    for (int j = 1; j <= tot && i * p[j] <= N; ++j) {
      flg[i * p[j]] = true;
      if (i % p[j] == 0) {
        g[i * p[j]] =
            g[i] + (g[i] - g[i / p[j]]) * p[j] * p[j];  // 代入推出来的式子
        break;
      }
      g[i * p[j]] = g[i] * g[p[j]];
    }
  }
}

using std::cin;
using std::cout;

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  int T, n;
  solve();  // 预处理g数组
  cin >> T;
  for (int i = 1; i <= T; ++i) {
    cin >> n;
    cout << (g[n] + 1) * n / 2 << '\n';
  }
  return 0;
}

「BZOJ 2154」Crash 的数字表格

求值(对 20101009 取模)

i=1nj=1mlcm(i,j)(n,m107)

易知原式等价于

i=1nj=1mijgcd(i,j)

枚举最大公因数 d,显然两个数除以 d 得到的数互质

i=1nj=1mdi,dj,gcd(id,jd)=1ijd

非常经典的 gcd 式子的化法

d=1ndi=1ndj=1md[gcd(i,j)=1] ij

后半段式子中,出现了互质数对之积的和,为了让式子更简洁就把它拿出来单独计算。于是我们记

sum(n,m)=i=1nj=1m[gcd(i,j)=1] ij

接下来对 sum(n,m) 进行化简。首先枚举约数,并将 [gcd(i,j)=1] 表示为 ε(gcd(i,j))

d=1ndindjmμ(d)ij

i=idj=jd,显然式子可以变为

d=1nμ(d)d2i=1ndj=1mdij

观察上式,前半段可以预处理前缀和;后半段又是一个范围内数对之和,记

g(n,m)=i=1nj=1mij=n(n+1)2×m(m+1)2

可以 Θ(1) 求解

至此

sum(n,m)=d=1nμ(d)d2g(nd,md)

我们可以 nnd 数论分块求解 sum(n,m) 函数。

在求出 sum(n,m) 后,回到定义 sum 的地方,可得原式为

d=1ndsum(nd,md)

可见这又是一个可以数论分块求解的式子!

本题除了推式子比较复杂、代码细节较多之外,是一道很好的莫比乌斯反演练习题!(上述过程中,默认 nm

时间复杂度:Θ(n+m)(瓶颈为线性筛)

代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <algorithm>
#include <iostream>
using namespace std;

constexpr int N = 1e7;
constexpr int mod = 20101009;
int n, m, mu[N + 5], p[N / 10 + 5], sum[N + 5];
bool flg[N + 5];

int Sum(int x, int y) {
  return ((long long)1 * x * (x + 1) / 2 % mod) *
         ((long long)1 * y * (y + 1) / 2 % mod) % mod;
}

int func(int x, int y) {
  int res = 0;
  int j;
  for (int i = 1; i <= min(x, y); i = j + 1) {
    j = min(x / (x / i), y / (y / i));
    res = (res + (long long)1 * (sum[j] - sum[i - 1] + mod) *
                     Sum(x / i, y / i) % mod) %
          mod;  //+mod防负数
  }
  return res;
}

int solve(int x, int y) {
  int res = 0;
  int j;
  for (int i = 1; i <= min(x, y); i = j + 1) {  // 整除分块处理
    j = min(x / (x / i), y / (y / i));
    res = (res + (long long)1 * (j - i + 1) * (i + j) / 2 % mod *
                     func(x / i, y / i) % mod) %
          mod;  // !每步取模防爆
  }
  return res;
}

void init() {  // 线性筛
  mu[1] = 1;
  int tot = 0, k = min(n, m);
  for (int i = 2; i <= k; ++i) {
    if (!flg[i]) {
      p[++tot] = i;
      mu[i] = -1;
    }
    for (int j = 1; j <= tot && i * p[j] <= k; ++j) {
      flg[i * p[j]] = true;
      if (i % p[j] == 0) {
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = -mu[i];
    }
  }
  for (int i = 1; i <= k; ++i)
    sum[i] = (sum[i - 1] + (long long)1 * i * i % mod * (mu[i] + mod)) % mod;
}

int main() {
  cin >> n >> m;
  init();
  cout << solve(n, m) << '\n';
}

「SDOI2015」约数个数和

多组数据,求

i=1nj=1md(ij)(n,m,T5×104)

其中 d(n)=in1d(n) 表示 n 的约数个数

要推这道题首先要了解 d 函数的一个特殊性质

d(ij)=xiyj[gcd(x,y)=1]

再化一下这个式子

d(ij)=xiyj[gcd(x,y)=1]=xiyjpgcd(x,y)μ(p)=p=1min(i,j)xiyj[pgcd(x,y)]μ(p)=pi,pjμ(p)xiyj[pgcd(x,y)]=pi,pjμ(p)xipyjp1=pi,pjμ(p)d(ip)d(jp)

将上述式子代回原式

i=1nj=1md(ij)=i=1nj=1mpi,pjμ(p)d(ip)d(jp)=p=1min(n,m)i=1nj=1m[pi,pj]μ(p)d(ip)d(jp)=p=1min(n,m)i=1npj=1mpμ(p)d(i)d(j)=p=1min(n,m)μ(p)i=1npd(i)j=1mpd(j)=p=1min(n,m)μ(p)S(np)S(mp)(S(n)=i=1nd(i))

那么 O(n) 预处理 μ,d 的前缀和,O(n) 分块处理询问,总复杂度 O(n+Tn).

代码实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <algorithm>
#include <iostream>
using namespace std;
constexpr long long N = 5e4 + 5;
long long n, m, T, pr[N], mu[N], d[N], t[N],
    cnt;  // t 表示 i 的最小质因子出现的次数
bool bp[N];

void prime_work(long long k) {
  bp[0] = bp[1] = true, mu[1] = 1, d[1] = 1;
  for (long long i = 2; i <= k; i++) {  // 线性筛
    if (!bp[i]) pr[++cnt] = i, mu[i] = -1, d[i] = 2, t[i] = 1;
    for (long long j = 1; j <= cnt && i * pr[j] <= k; j++) {
      bp[i * pr[j]] = true;
      if (i % pr[j] == 0) {
        mu[i * pr[j]] = 0;
        d[i * pr[j]] = d[i] / (t[i] + 1) * (t[i] + 2);
        t[i * pr[j]] = t[i] + 1;
        break;
      } else {
        mu[i * pr[j]] = -mu[i];
        d[i * pr[j]] = d[i] << 1;
        t[i * pr[j]] = 1;
      }
    }
  }
  for (long long i = 2; i <= k; i++)
    mu[i] += mu[i - 1], d[i] += d[i - 1];  // 求前缀和
}

long long solve() {
  long long res = 0, mxi = min(n, m);
  for (long long i = 1, j; i <= mxi; i = j + 1) {  // 整除分块
    j = min(n / (n / i), m / (m / i));
    res += d[n / i] * d[m / i] * (mu[j] - mu[i - 1]);
  }
  return res;
}

int main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  cin >> T;
  prime_work(50000);  // 预处理
  while (T--) {
    cin >> n >> m;
    cout << solve() << '\n';
  }
  return 0;
}

莫比乌斯反演扩展

结尾补充一个莫比乌斯反演的非卷积形式。

对于数论函数 f,g 和完全积性函数 tt(1)=1

f(n)=i=1nt(i)g(ni)g(n)=i=1nμ(i)t(i)f(ni)

我们证明一下

g(n)=i=1nμ(i)t(i)f(ni)=i=1nμ(i)t(i)j=1nit(j)g(nij)=i=1nμ(i)t(i)j=1nit(j)g(nij)=T=1ni=1nμ(i)t(i)j=1ni[ij=T]t(j)g(nT)【先枚举 ij 乘积】=T=1niTμ(i)t(i)t(Ti)g(nT)j=1ni[ij=T]对答案的贡献为 1,于是省略】=T=1ng(nT)iTμ(i)t(i)t(Ti)=T=1ng(nT)iTμ(i)t(T)【t 是完全积性函数】=T=1ng(nT)t(T)iTμ(i)=T=1ng(nT)t(T)ε(T)μ1=ε=g(n)t(1)【当且仅当 T=1,ε(T)=1时】=g(n)

参考文献

algocode 算法博客