【多项式】快速莫比乌斯变换(FMT)
快速莫比乌斯变换(Fast Möbius Transform, FMT),有时也称为快速子集卷积或子集和变换,是处理集合幂级数(Set Power Series)的一种高效算法。它在组合数学、图论、动态规划等领域有广泛应用,尤其在处理与子集相关的卷积问题时非常关键。
FMT 的核心思想是利用莫比乌斯反演,将子集卷积(Subset Convolution)从朴素的 O(3n)O(3^n)O(3n) 优化到 O(n⋅2n)O(n \cdot 2^n)O(n⋅2n)。
一、问题背景:子集卷积(Subset Convolution)
1.1 什么是子集卷积?
给定两个定义在集合 U={1,2,…,n}U = \{1, 2, \dots, n\}U={1,2,…,n} 的幂集上的函数 f,g:2U→Rf, g: 2^U \to \mathbb{R}f,g:2U→R,我们定义它们的子集卷积 h=f∗gh = f * gh=f∗g 为:
h(S)=∑T⊆Sf(T)⋅g(S∖T) h(S) = \sum_{T \subseteq S} f(T) \cdot g(S \setminus T) h(S)=T⊆S∑f(T)⋅g(S∖T)
注意:这里的 T∩(S∖T)=∅T \cap (S \setminus T) = \emptysetT∩(S∖T)=∅,即 TTT 和 S∖TS \setminus TS∖T 不相交。这与普通的或卷积(T∪R=ST \cup R = ST∪R=S,但 T∩RT \cap RT∩R 可能非空)不同。
子集卷积要求两个子集不相交,因此它比普通的或卷积更“严格”。
1.2 朴素算法的复杂度
直接计算每个 S⊆US \subseteq US⊆U 的 h(S)h(S)h(S),需要枚举所有 T⊆ST \subseteq ST⊆S,共有 2∣S∣2^{|S|}2∣S∣ 个子集。对所有 SSS 求和:
∑k=0n(nk)2k=3n \sum_{k=0}^{n} \binom{n}{k} 2^k = 3^n k=0∑n(kn)2k=3n
所以朴素算法复杂度为 O(3n)O(3^n)O(3n),当 n>20n > 20n>20 时就不可接受了。
二、FMT 的核心思想
FMT 的目标是将子集卷积转化为逐点乘积,类似于 FFT 将多项式乘法转化为点值乘法。
但子集卷积的难点在于:不相交性。普通或卷积可以通过 FWT(快速沃尔什变换)解决,但无法直接处理不相交条件。
FMT 的解决方案是:按集合大小分层。
三、分层表示(关键步骤)
我们引入一个新的维度:集合的大小。
定义:
fk(S)={f(S)if ∣S∣=k0otherwise f_k(S) = \begin{cases} f(S) & \text{if } |S| = k \\ 0 & \text{otherwise} \end{cases} fk(S)={f(S)0if ∣S∣=kotherwise
即 fkf_kfk 只在大小为 kkk 的集合上保留原值,其余为 0。
于是,原函数 f(S)f(S)f(S) 可以表示为:
f(S)=∑k=0nfk(S) f(S) = \sum_{k=0}^{n} f_k(S) f(S)=k=0∑nfk(S)
但更重要的是,子集卷积可以写成:
h(S)=∑i=0n∑j=0n[i+j=∣S∣]∑T⊆S,∣T∣=ifi(T)gj(S∖T) h(S) = \sum_{i=0}^{n} \sum_{j=0}^{n} [i + j = |S|] \sum_{T \subseteq S, |T|=i} f_i(T) g_j(S \setminus T) h(S)=i=0∑nj=0∑n[i+j=∣S∣]T⊆S,∣T∣=i∑fi(T)gj(S∖T)
这启发我们:对每个大小 kkk,分别计算 fkf_kfk 和 gkg_kgk 的或卷积,然后在满足 i+j=ki+j = ki+j=k 时合并。
四、FMT 的具体步骤
FMT 本质上是对每个大小层 kkk,对函数 fkf_kfk 做一次快速沃尔什变换(FWT)的子集和变换。
4.1 子集和变换(Sum Over Subsets, SOS)
对于函数 f:2n→Rf: 2^n \to \mathbb{R}f:2n→R,其子集和变换(也叫 zeta 变换)定义为:
f^(S)=∑T⊆Sf(T) \hat{f}(S) = \sum_{T \subseteq S} f(T) f^(S)=T⊆S∑f(T)
这个变换可以用 SOS DP 在 O(n⋅2n)O(n \cdot 2^n)O(n⋅2n) 时间内完成。
算法(升维法):
for(int i = 0; i < n; i++) {for(int mask = 0; mask < (1<<n); mask++) {if(mask >> i & 1) {f[mask] += f[mask ^ (1<<i)];}}
}
这就是 FMT 的核心变换。
4.2 FMT 的完整流程
我们要计算 h=f∗gh = f * gh=f∗g,其中 ∗*∗ 是子集卷积。
步骤:
-
分层:将 fff 和 ggg 按集合大小分层,得到 f0,f1,…,fnf_0, f_1, \dots, f_nf0,f1,…,fn 和 g0,g1,…,gng_0, g_1, \dots, g_ng0,g1,…,gn。
-
对每层做 FMT(子集和变换):
对每个 kkk,计算 f^k(S)=∑T⊆Sfk(T)\hat{f}_k(S) = \sum_{T \subseteq S} f_k(T)f^k(S)=∑T⊆Sfk(T)
同理计算 g^k(S)\hat{g}_k(S)g^k(S) -
逐点卷积:
对每个集合 SSS 和每个 kkk,计算:
h^k(S)=∑i+j=kf^i(S)⋅g^j(S) \hat{h}_k(S) = \sum_{i+j=k} \hat{f}_i(S) \cdot \hat{g}_j(S) h^k(S)=i+j=k∑f^i(S)⋅g^j(S) -
逆变换(莫比乌斯反演):
对每个 kkk,对 h^k\hat{h}_kh^k 做莫比乌斯反演(即子集和变换的逆),得到 hkh_khk:
hk(S)=∑T⊆Sμ(T,S)h^k(T) h_k(S) = \sum_{T \subseteq S} \mu(T, S) \hat{h}_k(T) hk(S)=T⊆S∑μ(T,S)h^k(T)
其中 μ(T,S)=(−1)∣S∖T∣\mu(T,S) = (-1)^{|S \setminus T|}μ(T,S)=(−1)∣S∖T∣,所以逆变换为:for(int i = 0; i < n; i++) {for(int mask = 0; mask < (1<<n); mask++) {if(mask >> i & 1) {f[mask] -= f[mask ^ (1<<i)];}} }
-
合并结果:h(S)=h∣S∣(S)h(S) = h_{|S|}(S)h(S)=h∣S∣(S)
五、C++ 实现
下面我们实现一个完整的 FMT 来计算子集卷积 h=f∗gh = f * gh=f∗g。
#include <iostream>
#include <vector>
using namespace std;typedef long long ll;
const int MAXN = 20;// 快速莫比乌斯变换(子集和变换): f[S] += f[T] for all T ⊂ S
void FMT(vector<ll>& f, int n, bool inverse = false) {for (int i = 0; i < n; i++) {for (int mask = 0; mask < (1 << n); mask++) {if (mask & (1 << i)) {if (!inverse) {f[mask] += f[mask ^ (1 << i)];} else {f[mask] -= f[mask ^ (1 << i)];}}}}
}// 子集卷积: h[S] = sum_{T ⊆ S, T ∩ (S\T) = ∅} f[T] * g[S\T]
vector<ll> subset_convolution(const vector<ll>& f, const vector<ll>& g, int n) {// 分层: f_layer[k][mask] 只在 |mask| == k 时有值vector<vector<ll>> f_layer(n + 1, vector<ll>(1 << n, 0));vector<vector<ll>> g_layer(n + 1, vector<ll>(1 << n, 0));// 填充分层数组for (int mask = 0; mask < (1 << n); mask++) {int cnt = __builtin_popcount(mask);f_layer[cnt][mask] = f[mask];g_layer[cnt][mask] = g[mask];}// 对每一层做 FMT(子集和变换)for (int k = 0; k <= n; k++) {FMT(f_layer[k], n, false);FMT(g_layer[k], n, false);}// 逐点卷积: h_layer[k] = sum_{i+j=k} f_layer[i] * g_layer[j]vector<vector<ll>> h_layer(n + 1, vector<ll>(1 << n, 0));for (int i = 0; i <= n; i++) {for (int j = 0; j <= n; j++) {int k = i + j;if (k > n) continue;for (int mask = 0; mask < (1 << n); mask++) {h_layer[k][mask] += f_layer[i][mask] * g_layer[j][mask];}}}// 逆变换:对每一层做莫比乌斯反演for (int k = 0; k <= n; k++) {FMT(h_layer[k], n, true);}// 合并结果:h[mask] = h_layer[|mask|][mask]vector<ll> h(1 << n, 0);for (int mask = 0; mask < (1 << n); mask++) {int cnt = __builtin_popcount(mask);h[mask] = h_layer[cnt][mask];}return h;
}// 测试样例
int main() {int n = 3;vector<ll> f(1 << n), g(1 << n);// 示例:f[S] = 1, g[S] = 1for (int i = 0; i < (1 << n); i++) {f[i] = 1;g[i] = 1;}auto h = subset_convolution(f, g, n);cout << "Subset Convolution Result:\n";for (int mask = 0; mask < (1 << n); mask++) {cout << "h(" << mask << ") = " << h[mask] << endl;}return 0;
}
六、复杂度分析
- 分层:O(n⋅2n)O(n \cdot 2^n)O(n⋅2n)
- 每层 FMT:O(n⋅2n)O(n \cdot 2^n)O(n⋅2n),共 n+1n+1n+1 层 → O(n2⋅2n)O(n^2 \cdot 2^n)O(n2⋅2n)
- 逐点卷积:(n+1)2⋅2n=O(n2⋅2n)(n+1)^2 \cdot 2^n = O(n^2 \cdot 2^n)(n+1)2⋅2n=O(n2⋅2n)
- 逆变换:O(n2⋅2n)O(n^2 \cdot 2^n)O(n2⋅2n)
- 合并:O(2n)O(2^n)O(2n)
总复杂度:O(n2⋅2n)O(n^2 \cdot 2^n)O(n2⋅2n)
相比朴素 O(3n)O(3^n)O(3n),当 n=20n=20n=20 时:
- 320≈3.48×1093^{20} \approx 3.48 \times 10^9320≈3.48×109
- 202⋅220≈400⋅106=4×10820^2 \cdot 2^{20} \approx 400 \cdot 10^6 = 4 \times 10^8202⋅220≈400⋅106=4×108
快了一个数量级!
七、应用场景
- 独立集计数:图中大小为 kkk 的独立集个数。
- 哈密顿路径计数:通过子集卷积优化状态转移。
- 生成函数合并:多个集合生成函数的不相交并。
- 树上的背包问题:合并子树信息时要求不相交。
八、总结
概念 | 说明 |
---|---|
FMT | 快速莫比乌斯变换,用于高效计算子集卷积 |
核心 | 按集合大小分层 + 子集和变换(zeta 变换) |
变换类型 | 类似于 FWT,但用于处理不相交并 |
复杂度 | O(n2⋅2n)O(n^2 \cdot 2^n)O(n2⋅2n) |
关键操作 | FMT (正变换)和 FMT(inverse=true) (逆变换) |
九、扩展:快速莫比乌斯变换的其他形式
FMT 广义上可以指任何基于莫比乌斯反演的快速变换,包括:
- 子集和变换
- 超集和变换(sum over supersets)
- 整除关系上的莫比乌斯变换(用于数论函数)
但在算法竞赛中,“FMT” 通常特指子集卷积的快速算法。
十、常见误区
- FMT ≠ FWT:FWT 处理或/与/异或卷积,FMT 处理子集卷积(不相交并)。
- 必须分层:如果不按大小分层,无法保证不相交。
- 逆变换是减法:莫比乌斯反演是容斥,用减法还原。