差分与前缀和

前情提要:学弟说想学莫反,于是偶先写一个入门教程,后面再写不入门的。欢迎大家提出建议,请大力喷!

差分、前缀和作为微分、积分在离散空间中的对应形式,还是相当有用的。

Part 1

1D 序列 / 一维线性空间

这是我们最熟悉的场景,说给定了一个序列 \(a=(a_1,a_2 \dots a_n)\),可以定义差分、前缀和两种运算/算子。

定义差分算子 \(\Delta\) 作用效果是 \((\Delta a)_i = a_i - a_{i-1}\)。
定义前缀和算子 \(\Delta^{-1}\) 作用效果是 \((\Delta^{-1} a)_i = \sum_{x=1}^i a_x\)。

这个符号特别难打,后面基本就不用了,只是想说这两种操作是互逆的。

这里注意一个细节,学过微积分的同学会知道,我们先差分再前缀和,应该会多出来一个不能确定的 \(C\)。
举个例子就是 \((1,2,3), (10,11,12)\) 的差分都是 \((1,1)\),再做前缀和得到的是 \((C, C+1, C+2)\) 这样一簇东西。

这样一来,我们的差分算子不是单射,疑似有点太垃圾了。
所以我们总是额外规定 \(a_0 = 0\),这样在差分的时候,可以额外保存这个常数 \(C\) 的具体信息。
这样一来 \(\Delta\) 和 \(\Delta^{-1}\) 之间也就可以交换次序了,即 \(\Delta \Delta^{-1} = \Delta^{-1} \Delta = I\)。当然这个很平凡。

Q1:给定长为 \(n\) 的序列 \(a_i\),有 \(q\) 次操作,支持区间加一个数,全部结束后,你需要输出序列。
Sol:维护差分序列,最后前缀和还原回来就行,\(O(n+q)\)。

Q2:给定长为 \(n\) 的序列 \(a_i\),有 \(q\) 次操作,支持区间加等差数列,全部结束后,你需要输出序列。
Sol:维护二阶差分序列,最后二阶前缀和还原回来就行,\(O(n+q)\)。

反演

反演的意思是,通过一个已知求和的序列,反向把原序列解出来。所以差分其实就是一种反演。

假设原序列是 \(a\),前缀和数组是 \(A\),那么可以写出以下这种等式:

  • \(A_1 = a_1\),\(A_2 = a_1 + a_2\),\(A_3 = a_1 + a_2 + a_3\)

考虑把前缀和算子 \(\Delta^{-1}\) 写成矩阵乘法:

\[\begin{bmatrix} A_1 \\ A_2 \\ A_3 \\ A_4 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 1 & 1 & 0 \\ 1 & 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} a_1 \\ a_2 \\ a_3 \\ a_4 \end{bmatrix} \]

考虑把差分算子 \(\Delta\) 写成矩阵乘法:

\[\begin{bmatrix} a_1 \\ a_2 \\ a_3 \\ a_4 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ -1 & 1 & 0 & 0 \\ 0 & -1 & 1 & 0 \\ 0 & 0 & -1 & 1 \end{bmatrix} \begin{bmatrix} A_1 \\ A_2 \\ A_3 \\ A_4 \end{bmatrix} \]

观察这两个矩阵,会发现一个“巧合”,两者是互逆的。

也就是说反演,或者说求差分算子 \(\Delta\),其实就是求某个矩阵的逆矩阵。也可以认为反演就是容斥。我们后面还会反复用到这种刻画。

加法卷积

我们定义两个序列 \(a\) 和 \(b\) 的加法卷积为:

\[(a * b)_n = \sum_{i+j=n} a_i b_j \]

故可以使用卷积来描述前缀和、差分的算子:

  • 定义 \(I = (1,1,1,1 \dots 1)\) 表示常数函数,那么前缀和 \(A\) 就是 \(a * I\) 做卷积。
  • 定义 \(D = (1,-1,0,0 \dots 0)\) 表示差分函数,那么差分 \(a\) 就是 \(A * D\) 做卷积。
  • 定义 \(e = (1, 0,0,0 \dots 0)\) 为单位元函数。类似的,也有 \(I * D = e\) 互逆。

这里的常数函数有时也记作 \(\textbf{1}\) 或 \(\textbf{1}(n)\) 的形式。这种做法也可以视作是形式幂级数、生成函数。

高维前缀和 / 高维线性空间

先考虑 2D 的版本,这个也比较众所周知了:
前缀和,意思是,对于两个维度上,都满足下标都小于等于我的位置,它们的值要被加给我:

\[A_{x,y} = \sum_{i \le x} \sum_{j \le y} a_{i,j} \]

这里定义偏序关系是 \(\le\) 即整数的大小关系。

差分,即容斥,这里的容斥系数容易确定:若下标有 \(c\) 个维度上有减法,系数就是 \((-1)^c\):

\[a_{x,y} = A_{x,y} - A_{x,y-1} - A_{x-1,y} + A_{x-1,y-1} \]

可以把上式的减一换成别的,这样会容斥出来一个矩形范围。我们后面还会继续提到这点。

求前缀和的时候可以逐维度叠加,这里直接偷 OI wiki 的代码,以三维为例:

  for (int i = 1; i <= N1; ++i)
    for (int j = 1; j <= N2; ++j)
      for (int k = 1; k <= N3; ++k) a[i][j][k] += a[i][j][k - 1];

  for (int i = 1; i <= N1; ++i)
    for (int j = 1; j <= N2; ++j)
      for (int k = 1; k <= N3; ++k) a[i][j][k] += a[i][j - 1][k];

  for (int i = 1; i <= N1; ++i)
    for (int j = 1; j <= N2; ++j)
      for (int k = 1; k <= N3; ++k) a[i][j][k] += a[i - 1][j][k];

设 \(k\) 维空间的整体大小为 \(n\),则前缀和操作的复杂度为 \(O(kn)\),差分也一样。
(原地)差分的代码需要改 -=,并使枚举顺序反向(从大到小),因为我们希望等式右侧的变量还没有被还原回去,也可以理解为按顺序撤销。

SOSdp / 快速 Zeta 变换

考虑这样一个问题,我们有一个大小为 \(n\) 的集合 \(S\),有一个在全体子集上有定义的 \(f\),希望对每个子集 \(S'\) 都计算:

\[g(S') = \sum_{T \subseteq S'} f(T) \]

枚举每个子集 \(S'\) 再枚举其子集,这样的复杂度是 \(O(3^n)\) 的,但我们可以做得更好。

不妨将每个元素视作一个维度,将不含/含它分别设为 \(\{0,1\}\),则得到了 \(n\) 维空间,总状态数为 \(2^n\)。
我们是这样描述前缀和的:对于每个维度上,都满足下标都小于等于我的位置,它们的值要被加给我。
我们定义偏序关系 \(\subseteq\) 即集合的包含关系,子集和就变成了前缀和的描述,可以得到 \(O(n 2^n)\) 的算法。

  g = f; // 先把 f(T) 加给 g(T),类比一下 a 和 A

  for (int i = 0; i < n; ++i) {             // 考虑每个维度(逐维度叠加)
    for (int st = 0; st < (1 << n); ++st) { // 考虑每个状态(从小到大)
      if ((st >> i) & 1) {                  // 该状态第 i 个维度是 1
        // g[... 1 ...] += g[... 0 ...].
        g[st] += g[st ^ (1 << i)];          // 右侧的状态一定已经算过了
      }
    }
  }

一般来说,如果希望差分回去的话,首先需要改成 -=,同时翻转状态的枚举顺序(从大到小)。
但由于这里每个维度只有 \(\{0,1\}\),我们并不需要翻转枚举顺序,因为右侧的变量在枚举每个 \(i\) 时不可能被修改。

Part 2

算术基本定理 / 素数基

也就是整数唯一分解定理,任何正整数 \(n\) 都可以唯一表示为素数的乘积:

\[n = 2^{c_1} \cdot 3^{c_2} \cdot 5^{c_3} \cdot 7^{c_4} \cdots \]

我们不妨把所有素数看做一组基,那么每个正整数 \(n\) 都对应了一个向量:

\[\vec{n} = (c_1, c_2, c_3, c_4, \dots) \]

考虑一个数 \(n\) 的所有约数 \(d\),实际上遍历了每一个维度上都 \(\le\) 向量 \(\vec{n}\) 的 \(\vec{d}\)。

积性函数 / 正交分解

数论函数(也称算术函数)指定义域为正整数的函数。数论函数也可以视作一个数列。

积性函数是一类特殊的数论函数,满足:\(f(1) = 1\),如果 \(\gcd(a, b) = 1\),则 \(f(ab) = f(a)f(b)\)。

这类函数我们经常使用,从素数基的视角来看,\(\gcd(a, b) = 1\) 意味着 \(\vec{a}\) 和 \(\vec{b}\) 是正交的。
这样一来,我们的 \(f(n)\) 总是可以这样表示:

\[f(\vec{n}) = f(c_1, 0, 0\dots) \times f(0, c_2, 0\dots) \times f(0, 0, c_3\dots) \]

换句话说,对于具有积性的函数,我们只要研究它在单个素数轴上的性质,就足够考察它在所有整数上的性质了。

注:众所周知 \(\gcd(a,b) = 1\) 还可以写作 \(a \perp b\),这其实正是想要表示两者正交的意思。

狄利克雷卷积 / 乘法卷积

给定两个数论函数 \(f\) 和 \(g\),它们的狄利克雷卷积定义为:$$(f * g)(n) = \sum_{d\vert{}n} f(d) g\left(\frac{n}{d}\right)$$

我们考虑把数字表示成素数基的向量,那么 \(\frac{n}{d}\) 的坐标恰好是向量相减 \(\vec{n} - \vec{d}\)。上面的公式就变成了:

\[(f * g)(\vec{n}) = \sum_{\vec{0} \le \vec{d} \le \vec{n}} f(\vec{d}) g(\vec{n} - \vec{d}) \]

对照一下,可以发现其实就是加法卷积公式,在高维空间的推广。

若只求一项 \((f * g)(n)\) 的值,分解因子就能做到 \(O(\sqrt n)\);否则可以暴力枚举,在 \(O(n \log n)\) 时间求解每项:

  for (int k = 1; k <= n; k++)
    for (int d = 1; k * d <= n; d++)
      h[k * d] += f[k] * g[d];

狄利克雷前缀和

令 \(g(n) = 1(n)\),然后和 \(f\) 做卷积,可以发现其实 \(F(\vec{n})\) 就是 \(f\) 在这个空间里的前缀和:

\[F(n) = \sum_{d\vert{}n} f(d) \cdot 1 \quad \implies \quad F(\vec{n}) = \sum_{\vec{0} \le \vec{d} \le \vec{n}} f(\vec{d}) \]

我们可以逐维度完成前缀和,逻辑和埃筛一致,故复杂度也为 \(O( n \log\log n)\),代码如下:

  F = f; // 先把 f(i) 加给 F(i)
  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];
    }
  } // 差分时记得反向枚举内层的 xy

初学的时候可能会写出 F[xy] += F[xy - x] 这种错误,只要提醒自己,我们的整数乘除才是向量上的加减法。

不妨将狄利克雷前缀和 \(A_n = \sum_{d\vert{}n} a_d\) 用矩阵乘法表示:

\[\begin{bmatrix} A_1 \\ A_2 \\ A_3 \\ A_4 \\ A_5 \\ A_6 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 & 0 \\ 1 & 0 & 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 & 1 & 0 \\ 1 & 1 & 1 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} a_1 \\ a_2 \\ a_3 \\ a_4 \\ a_5 \\ a_6 \end{bmatrix} \]

我们如果希望通过 \(A\) 反求 \(a\),就需要求上面的逆矩阵,而在“素数基”张成的空间里,逆矩阵的系数亦可以用莫比乌斯函数 \(\mu\) 确定。

莫比乌斯反演 / 容斥

回忆 2D 矩阵的容斥:下标要么不变要么减一;若下标有 \(c\) 个维度上有减法,系数就是 \((-1)^c\):

\[a_{x,y} = A_{x,y} - A_{x,y-1} - A_{x-1,y} + A_{x-1,y-1} \]

在素数基下,我们也可以定义一样的规则,和普通容斥无异:

  • 若无减法,则系数为 \(1\)
  • 若有 \(c\) 个素数维度减一,则系数为 \((-1)^c\)
  • 如果有位置出现了 \(-2\) 等其他值,系数为 \(0\)

我们令被减去的素数之乘积 \(\prod p_i^{c_i} = n\),则可以这样定义莫比乌斯函数(容斥系数):

\[\mu(n) = \begin{cases} 1, & n = 1, \\ (-1)^k, & n \text{ 是 } k \text{ 个不同质数的乘积}, \\ 0, & n \text{ 含有平方因子}. \\ \end{cases} \]

这样一来就可以更简洁地表达 \(f\) 与其狄利克雷前缀和 \(F\) 的关系:

\[\begin{align} F(n) &= \sum_{d \mid n} f(d) \\ f(n) &= \sum_{d \mid n} \mu(d) F(\frac{n}{d}) \\ \end{align} \]

这就是莫反的约数形式。只需要记住,这里的除法可以写成向量减法,上式就会非常容易理解,并且可以当场推导。
转化为矩阵语言,前一节我们想要求得的逆矩阵:第 \(i\) 行、第 \(d\) 列的元素在 \(d \mid i\) 时为 \(\mu(i/d)\),若不能整除则为 \(0\)。

接下来我们介绍莫反的倍数形式,这是与莫反的约数形式一样热门的形式:(TOP2 常用!)

\[\begin{align} F(n) &= \sum_{k = 1} f(kn) \\ f(n) &= \sum_{k = 1} \mu(k) F(kn) \\ \end{align} \]

即狄利克雷后缀和,以及其反演形式。求和的上界是到 \(kn\) 超过定义域为止。
怎么理解 \(kn\) 呢,前缀和在偏序上小于我的是做除法,那么后缀和就是乘法。

常见公式

综上,我们总结一下可以和这篇入门教程产生关联的公式,并简单推导,说明正确性。

约数个数

定义 \(d(n)\) 表示 \(n\) 的约数个数,是积性函数。写成前缀和就是 \(d(n) = \sum_{d\vert{}n} 1\)。

用素数基的看法就是从原点 \(\vec{0}\) 到 \(\vec{n} = (c_1, c_2, c_3, \dots)\) 构成的超长方体里,一共有多少个整数格点,故:

\[d(n) = (c_1 + 1)(c_2 + 1)(c_3 + 1)\dots \]

考虑补成乘法卷积形式 \(d(n) = \sum_{d\vert{}n} 1 \cdot 1\),我们也就得到了 \(d = \textbf{1} * \textbf{1}\),如何理解呢:
把 \(d(n)\) 当做是对一个全 \(1\) 的空间做前缀和以后在 \(\vec{n}\) 处的值,可以拿 1D 数组类比一下。

莫比乌斯函数

莫比乌斯函数 \(\mu(n)\) 是积性函数。莫比乌斯函数有一个非常重要的恒等性质:

\[[ n = 1 ] = \sum_{ d \mid n } \mu(d) \]

怎么理解呢,我们不妨把等式右边写成 $ \sum_{ d \mid n } \mu(d) \cdot 1$,考察其具体含义:
就是对一个全 \(1\) 的空间做差分,结果只有原点 \(n = 1 = (0,0,0 \dots)\) 的值是 \(1\),可以拿 1D 数组类比一下。

也就是证明了 \(\mu * \textbf{1} = e\),卷 \(1\) 是前缀和,卷 \(\mu\) 是差分,这两个操作互逆,结果是单位元 \(e\)。

欧拉反演

欧拉函数 \(\varphi(n) = \sum_{i=1}^n [i \perp n]\) 是一个积性函数,表示 \(1 \dots n\) 的数字中和 \(n\) 互质的数量。

有一个常用的欧拉反演公式 \(\sum_{d\vert{}n} \varphi(d) = n\),即 \(\varphi\) 在高维空间中的前缀和,我们试着证明它。

由于具有积性,我们只需要单独考察一个维度 \(p\) 上坐标为 \(k\) 时的情形,即 \(n = p^k\) 时:

\[\begin{align*} \varphi(1) &= 1 \\ \varphi(p) &= p - 1 \\ \varphi(p^2) &= p^2 - p \\ &\dots \\ \varphi(p^k) &= p^k - p^{k-1} \end{align*} \]

把它们加起来,可以发现这里相邻项都相消了,恰好得到了欧拉反演的表述:

\[\sum_{i=0}^k \varphi(p^i) = p^k \]

考虑补成乘法卷积形式 \(\sum_{d\vert{}n} \varphi(d) \cdot 1= n\),我们也就得到了 \(\varphi * \textbf{1} = \text{id}\)。

\(\varphi = \mu * \text{id}\)

试证明下面的恒等式: $$\varphi(n) = \sum_{d\vert{}n} \mu(d) \frac{n}{d}$$

我们上一段说明了 \(\varphi * \textbf{1} = \text{id}\),在两边同时卷上 \(\mu\),有:

\[\varphi * (\textbf{1} * \mu) = \text{id} * \mu \]

我们知道卷 \(\textbf{1}\) 是前缀和,而卷 \(\mu\) 是差分,故 \(\textbf{1}\) 和 \(\mu\) 互逆:

\[\varphi = \text{id} * \mu \]

得证。


感谢阅读,请喷。

posted @ 2026-09-28 02:00  Aurora5090  阅读(35)  评论(0)    收藏  举报