当前位置: 首页 > news >正文

【多项式】快速莫比乌斯变换(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(n2n)


一、问题背景:子集卷积(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:2UR,我们定义它们的子集卷积 h=f∗gh = f * gh=fg 为:

h(S)=∑T⊆Sf(T)⋅g(S∖T) h(S) = \sum_{T \subseteq S} f(T) \cdot g(S \setminus T) h(S)=TSf(T)g(ST)

注意:这里的 T∩(S∖T)=∅T \cap (S \setminus T) = \emptysetT(ST)=,即 TTTS∖TS \setminus TST 不相交。这与普通的或卷积T∪R=ST \cup R = STR=S,但 T∩RT \cap RTR 可能非空)不同。

子集卷积要求两个子集不相交,因此它比普通的或卷积更“严格”。


1.2 朴素算法的复杂度

直接计算每个 S⊆US \subseteq USUh(S)h(S)h(S),需要枚举所有 T⊆ST \subseteq STS,共有 2∣S∣2^{|S|}2S 个子集。对所有 SSS 求和:

∑k=0n(nk)2k=3n \sum_{k=0}^{n} \binom{n}{k} 2^k = 3^n k=0n(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=0nfk(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=0nj=0n[i+j=S]TS,T=ifi(T)gj(ST)

这启发我们:对每个大小 kkk,分别计算 fkf_kfkgkg_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:2nR,其子集和变换(也叫 zeta 变换)定义为:

f^(S)=∑T⊆Sf(T) \hat{f}(S) = \sum_{T \subseteq S} f(T) f^(S)=TSf(T)

这个变换可以用 SOS DPO(n⋅2n)O(n \cdot 2^n)O(n2n) 时间内完成。

算法(升维法):
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=fg,其中 ∗* 是子集卷积。

步骤:
  1. 分层:将 fffggg 按集合大小分层,得到 f0,f1,…,fnf_0, f_1, \dots, f_nf0,f1,,fng0,g1,…,gng_0, g_1, \dots, g_ng0,g1,,gn

  2. 对每层做 FMT(子集和变换)
    对每个 kkk,计算 f^k(S)=∑T⊆Sfk(T)\hat{f}_k(S) = \sum_{T \subseteq S} f_k(T)f^k(S)=TSfk(T)
    同理计算 g^k(S)\hat{g}_k(S)g^k(S)

  3. 逐点卷积
    对每个集合 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=kf^i(S)g^j(S)

  4. 逆变换(莫比乌斯反演)
    对每个 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)=TSμ(T,S)h^k(T)
    其中 μ(T,S)=(−1)∣S∖T∣\mu(T,S) = (-1)^{|S \setminus T|}μ(T,S)=(1)ST,所以逆变换为:

    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)];}}
    }
    
  5. 合并结果h(S)=h∣S∣(S)h(S) = h_{|S|}(S)h(S)=hS(S)


五、C++ 实现

下面我们实现一个完整的 FMT 来计算子集卷积 h=f∗gh = f * gh=fg

#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(n2n)
  • 每层 FMT:O(n⋅2n)O(n \cdot 2^n)O(n2n),共 n+1n+1n+1 层 → O(n2⋅2n)O(n^2 \cdot 2^n)O(n22n)
  • 逐点卷积:(n+1)2⋅2n=O(n2⋅2n)(n+1)^2 \cdot 2^n = O(n^2 \cdot 2^n)(n+1)22n=O(n22n)
  • 逆变换:O(n2⋅2n)O(n^2 \cdot 2^n)O(n22n)
  • 合并:O(2n)O(2^n)O(2n)

总复杂度:O(n2⋅2n)O(n^2 \cdot 2^n)O(n22n)

相比朴素 O(3n)O(3^n)O(3n),当 n=20n=20n=20 时:

  • 320≈3.48×1093^{20} \approx 3.48 \times 10^93203.48×109
  • 202⋅220≈400⋅106=4×10820^2 \cdot 2^{20} \approx 400 \cdot 10^6 = 4 \times 10^8202220400106=4×108

快了一个数量级!


七、应用场景

  1. 独立集计数:图中大小为 kkk 的独立集个数。
  2. 哈密顿路径计数:通过子集卷积优化状态转移。
  3. 生成函数合并:多个集合生成函数的不相交并。
  4. 树上的背包问题:合并子树信息时要求不相交。

八、总结

概念说明
FMT快速莫比乌斯变换,用于高效计算子集卷积
核心按集合大小分层 + 子集和变换(zeta 变换)
变换类型类似于 FWT,但用于处理不相交并
复杂度O(n2⋅2n)O(n^2 \cdot 2^n)O(n22n)
关键操作FMT(正变换)和 FMT(inverse=true)(逆变换)

九、扩展:快速莫比乌斯变换的其他形式

FMT 广义上可以指任何基于莫比乌斯反演的快速变换,包括:

  • 子集和变换
  • 超集和变换(sum over supersets)
  • 整除关系上的莫比乌斯变换(用于数论函数)

但在算法竞赛中,“FMT” 通常特指子集卷积的快速算法。


十、常见误区

  1. FMT ≠ FWT:FWT 处理或/与/异或卷积,FMT 处理子集卷积(不相交并)。
  2. 必须分层:如果不按大小分层,无法保证不相交。
  3. 逆变换是减法:莫比乌斯反演是容斥,用减法还原。
http://www.xdnf.cn/news/1404577.html

相关文章:

  • ⭐CVPR2025 自动驾驶半监督 LiDAR 分割新范式:HiLoTs 框架深度解析
  • Python 数据分析:计算,分组统计2,df.groupby()和grouped.agg()。听故事学知识点怎么这么容易?
  • 告别图片处理焦虑:用imgix实现智能、实时且高效的视觉媒体交付(含案例、截图)
  • 一键掌控三线资源:极简 Shell 脚本实现 CPU·磁盘·内存可视化巡检
  • SRE命令行兵器谱之二:lsof - 解密“端口被占用”与“文件句柄泄漏”的终极侦探
  • MySQL-事务(下)-MySQL事务隔离级别与MVCC
  • 2021-11-10 C++不变初心数
  • ans1语法的一个例子nt5inf.cat
  • 详解Vue2、Vue3与React的Diff算法
  • TuringComplete游戏攻略(2.2存储器)
  • spark.sparkContext.broadcast() 与 org.apache.spark.sql.functions.broadcast 的区别
  • Docker实战避坑指南:从入门到精通
  • 神经网络激活函数:从ReLU到前沿SwiGLU
  • 分分合合,门模块方案又兴起了
  • 用更少的数据识别更多情绪:低资源语言中的语音情绪识别新方法
  • Vue生命周期、工程化开发和脚手架、组件化开发
  • hubert模型代码分析
  • 聚中原·贸全国·达世界,2026郑州台球展8月15至17举办
  • 深入解析Nginx常见模块1
  • 世界模型的典型框架与分类
  • 如何提高存储过程的可维护性
  • wav2vec2.0模型代码分析
  • vite Rendering 10 pagesReferenceError: document is not defined
  • OpenCV 图像形态学操作与边缘检测实战指南
  • 深刻理解软硬件链接
  • 【MogDB】在刚发布的银河麒麟v11上安装MogDB
  • Unity游戏打包——GooglePlay手动传包
  • 微服务架构中的 “双保险“:服务保护与分布式事务解决方案实战
  • 配置vsc可用的C语言环境
  • 【开题答辩全过程】以 基于WEB的茶文化科普系统的设计与实现为例,包含答辩的问题和答案