【多项式】快速莫比乌斯变换(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] = 1
    for (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. 逆变换是减法:莫比乌斯反演是容斥,用减法还原。
### 傅里叶变换多项式函数的关系 傅里叶变换通常用于处理连续或离散信号,将信号从时域转换到频域。对于多项式函数而言,可以利用离散傅里叶变换(DFT)和快速傅里叶变换(FFT)来实现其在频域中的表示。 #### 多项式表示与傅里叶变换 一个多项式 $ P(x) $ 可以表示为: $$ P(x) = a_0 + a_1 x + a_2 x^2 + \cdots + a_{n-1} x^{n-1} $$ 其中 $ a_i $ 是多项式的系数。在离散傅里叶变换中,多项式可以通过对系数进行变换得到其频域表示。具体来说,离散傅里叶变换公式如下: $$ X_k = \sum_{n=0}^{N-1} x_n e^{-i 2\pi k n / N}, \quad k = 0, 1, \ldots, N-1 $$ 其中 $ x_n $ 是多项式的系数序列,$ X_k $ 是变换后的频域值。 #### 快速傅里叶变换(FFT) 快速傅里叶变换是一种高效的算法,用于计算离散傅里叶变换及其逆变换。FFT 的时间复杂度为 $ O(N \log N) $,相比于直接计算 DFT 的 $ O(N^2) $ 更加高效。Python 中的 `numpy` 库提供了 FFT 的实现,可以直接用于多项式函数的傅里叶变换。 ```python import numpy as np # 定义多项式系数 coefficients = [1, 2, 3, 4] # 进行快速傅里叶变换 fft_result = np.fft.fft(coefficients) # 输出结果 print("FFT Result:", fft_result) ``` #### 多项式乘法与傅里叶变换 傅里叶变换的一个重要应用是多项式乘法。两个多项式的乘积可以通过以下步骤计算: 1. 对两个多项式的系数分别进行傅里叶变换。 2. 将变换后的频域值相乘。 3. 对相乘的结果进行逆傅里叶变换,得到乘积多项式的系数。 这一过程利用了卷积定理,即时域中的卷积对应于频域中的乘积。 ```python # 定义两个多项式系数 poly1 = [1, 2, 3] poly2 = [4, 5, 6] # 进行快速傅里叶变换 fft_poly1 = np.fft.fft(poly1) fft_poly2 = np.fft.fft(poly2) # 频域相乘 product_fft = fft_poly1 * fft_poly2 # 逆快速傅里叶变换 product_poly = np.fft.ifft(product_fft) # 输出结果并取实部(忽略浮点误差) print("Product Polynomial Coefficients:", np.round(np.real(product_poly))) ``` #### 注意事项 尽管傅里叶变换可以用于多项式函数的分析,但在实际应用中需要注意以下几点: - **边界效应**:由于傅里叶变换假设输入信号是周期性的,因此可能会导致边界处的不连续性,从而引入高频噪声。 - **采样率**:为了准确地表示信号,采样率必须足够高,否则可能导致混叠现象。 - **零填充**:为了提高频率分辨率,可以在多项式系数后添加零值,但这不会改变多项式本身的性质。 通过这些方法,可以有效地将多项式函数转换到频域,并利用频域分析技术解决实际问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值