Typesetting math: 100%

Fast Möbius Transform 学习笔记 | FMT

小 Tips:在计算机语言中 = & / and = | / or

First. 定义

定义长度为 2n 的序列的 and 卷积 A=BCAi=jk=iBj×Ck

考虑快速计算

Second. 变换

定义长度为 2n 的序列的 Zeta 变换

A^i=jiAj

即子集和

它具有一下性质:

B^i×C^i=jiBj×kiCk=pijk=pBj×Ck=piAp=A^i

相当于 FFT 中的点值表示法,用以加速卷积过程

Third. 快速变换

暴力求解 A^ 最优时间复杂度为 3n,考虑加速

考虑 子集DP ,有一篇很好的博客[1],这里大概讲解一下。

其实 A^ 本质上是一个高维(n 维)前缀和

如果我们要求二维前缀和,显然可以通过一下代码实现:

// 1st
for(int i=1; i<=n; ++i)
    for(int j=1; j<=m; ++j)
        	a[i][j]+=a[i-1][j];
// 2nd
for(int i=1; i<=n; ++i)
    for(int j=1; j<=n; ++j)
        	a[i][j]+=a[i][j-1];

我们发现 1st 完成后 a[i][j] 表示的是 kiak,j,即 (i,j) 上方的元素和

那么 2nd 便是将 (i,j) 左边操作后的 kjai,k 累加到 a[i][j] 中,即完成二维前缀和


下面拓展到三维前缀和

// 1st
for(int i=1; i<=n; ++i)
    for(int j=1; j<=n; ++j)
        for(int k=1; k<=n; ++k)
            a[i][j][k]+=a[i-1][j][k];
// 2nd
for(int i=1; i<=n; ++i)
    for(int j=1; j<=n; ++j)
        for(int k=1; k<=n; ++k)
            a[i][j][k]+=a[i][j-1][k];
// 3rd
for(int i=1; i<=n; ++i)
    for(int j=1; j<=n; ++j)
        for(int k=1; k<=n; ++k)
            a[i][j][k]+=a[i][j][k-1];

类似地,1st,2nd 中处理除了第 k 平面中的二维前缀和,3rd 在立体空间中把他们加起来,成为三维前缀和


回到 Zeta 变换

A^i=jiAj

如果我们用二进制 (xn1xn2xn3 ... x2x1x0)2 表示集合 i,二进制 (yn1yn2yn3 ... y2y1y0)2 表示集合 j

那么 A^i 可以被视为 n 维前缀和,即

A^i=yn1xn1,yn2xn2, ..., y1x1,y0x0Aj

当然,下标都为 0/1

因此,我们有如下 Code:

for(int i=0; i<n; ++i)
    for(int j=0; j<(1<<n); ++j)
        if(j&(1<<i))
            a[j]+=a[j^(1<<i)];

以上 Code 的 naive 形式为:

for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(i1>0) a[i1][i2][...][in]+=a[i1-1][i2][...][in];
for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(i2>0) a[i1][i2][...][in]+=a[i1][i2-2][...][in];
...
for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(in>0) a[i1][i2][...][in]+=a[i1][i2][...][in-1];

不要质疑我代码编译不通过

因此,我们用 O(n2n) 的时间复杂度解决了 Zeta 变换

Fourth. 逆变换

warning:此处不是 莫比乌斯反演

众所周知,前缀和的逆运算即为差分

我们发现,只需要先将最后一维差分,即可将序列处理为 n1 维前缀和

for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(in>0) a[i1][i2][...][in]-=a[i1][i2][...][in-1];
...
for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(i2>0) a[i1][i2][...][in]-=a[i1][i2-2][...][in];
for(int i1=0; i1<2; ++i1) for(int i2=0; i2<2; ++i2) ... for(int in=0; in<2; ++in) if(i1>0) a[i1][i2][...][in]-=a[i1-1][i2][...][in];

但实际上因为前缀和都是无序的,因此我们直接正着做就可以啦

只需要把正变换的 += 改为 -= 就可以了

Fifth. 扩展

有时候,题目给的不是 ,而是 怎么办?

那我们重定义 Zeta 变换 为:

A^i=ijAj

再推一下式子:

B^i×C^i=ijBj×ikCk=ij,ikBj×Ck=ipjk=pBj×Ck=ipAp=A^i

变换即超集和,高维后缀和

求法也很简单,只用将源代码中的 if(j&(1<<i)) 该为 if(!(j&(1<<i))) 即可


怎么办?

快去学 FWT 吧

Last. 例题

再送你一个并/交集的小口诀:下并或,上交与


  1. 高维前缀和总结(sosdp) - heyuhhh - 博客园 (cnblogs.com) ↩︎

posted @   lnw143  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示