FWT & FMT(位运算卷积)学习笔记

它们两个的全名叫 快速沃尔什变换(FWT) 和 快速莫比乌斯变换(FMT),用来在 O(nlogn) 时间复杂度内求位运算卷积。
因为 FMT 能解决的问题是 FWT 的子集,所以这里不讲 FMT,把它拎出来是想说它们两个的区别。

参考资料:偶耶XJX-浅谈快速沃尔什变换(FWT)&快速莫比乌斯变换(FMT)zcxxn-多项式学习笔记

位运算卷积

位运算卷积,就是求下面这个问题:
给定长度为 n 的序列 A,B,求它们的卷积 Ci=jk=iAj×Bk,其中 and,or,xor 中的一种。这里如果把 换成加号就是多项式乘法。

Or

我们要求 Ci=j|k=iAj×Bk。这个柿子长得和多项式乘法差不多,所以试图仿照 FFT 的思路对序列变换进行构造。
fwti(A)=j|i=iAj。考虑这样构造有什么性质:

fwti(A)fwti(B)=(j|i=iAj)(k|i=iBk)=j|i=ik|i=kAjBk=(j|k)|i=iAjBk

继续推 fwt(C)

fwti(C)=j|i=iCj=j|i=ix|y=jAxBy=(x|y)|i=iAxBy

那么有:

fwti(C)=fwti(A)fwti(B)

这很好,只需要知道怎么转化 Afwt(A) 就解决问题了。

Afwt(A)

肯定不能按定义式求,这样搞是 n2 的。

考虑根据下标二进制下最高位的值是 0/1A 序列分治。我们定义最高位为 0 的序列叫 A0,另一半叫 A1

那么,fwt(A) 的前半部分,jk 的最高位也都只能取 0,即 fwt(A0);后半部分,jk 的最高位没有限制,所以即 fwt(A0)+fwt(A1)

merge(a,b) 表示把 ab 序列前后拼接在一起,则有:

fwt(A)=merge(fwt(A0),fwt(A0)+fwt(A1))

递归即可。当然更常见的写法是直接循环迭代。

fwt(A)A

把变换反过来,思路是差不多的:

我们这次把 fwt(A) 序列按二进制最高位分为 fwt(A0)fwt(A1)

同理地,A 的前半部分只由 fwt(A0) 得到,而后半部分的 fwt(A1) 里面两种都有,去掉 0 开头的就是 fwt(A1)fwt(A0)

那么有反演式(这里似乎也有的博客叫 ufwt):

Ifwt(A)=merge(Ifwt(A0),Ifwt(A1)Ifwt(A0))

代码实现时可以合并这两个过程。

void Or(int *a,int tp)
{
    for(int len=1;len<(1<<n);len<<=1)
        for(int i=0;i<(1<<n);i+=(len<<1))
            for(int j=0;j<len;j++) add(a[i+j+len],a[i+j]*tp);
}

And

和 Or 卷积本质相同,因此这里直接给出结论,读者可以自行证明。

fwt(A)=merge(fwt(A0)+fwt(A1),fwt(A0))

Ifwt(A)=merge(Ifwt(A1)Ifwt(A0),Ifwt(A0))

void And(int *a,int tp)
{
    for(int len=1;len<(1<<n);len<<=1)
        for(int i=0;i<(1<<n);i+=(len<<1))
            for(int j=0;j<len;j++) add(a[i+j],a[i+j+len]*tp);
}

FMT

插播一条 FMT!

不少博客说做 And 和 Or 卷积的 FWT 就是 FMT,这句话的正确性有待考据。

实际上,FMT 和 FWT 在前面构造的部分都是一样的,区别在于转化 Afwt(A) 的方式不同。已经知道,FWT 是基于分治加速运算,而 FMT 则是基于 DP 对这一步进行的加速。代码难度上也没有太大差别,具体是怎么加速的这里不讲了 qaq。

Xor

Xor 卷积就只能用 FWT 做了,而且也是最难理解的部分。

d(x) 表示 x 在二进制下 1 的个数。对 FWT 函数做如下定义:

fwti(A)=j(1)d(i&j)Aj

换句话讲,改变 d 的定义,令 d(x) 表示 1 的个数的奇偶性,偶为 0,奇为 1。那么这个式子可以写成:

fwti(A)=d(i&j)=0Ajd(i&j)=1Aj

所以根据 (i and k) xor (j and k)=(i xor j) and k,有:

fwti(A)fwti(B)=(d(i&j)=0Ajd(i&j)=1Aj)(d(i&k)=0Bkd(i&k)=1Bk)=d(i&j) xor d(i&k)=0AjBkd(i&j) xor d(i&k)=0AjBk=d(i&(j xor k))=0AjBkd(i&(j xor k))=1AjBk=fwti(C)

同样基于分治思想构造 Afwt(A) 的转移。这个东西实在过于抽象,感性理解,理解不了就记下来(

fwt(A)=merge(fwt(A0)+fwt(A1),fwt(A0)fwt(A1))

Ifwt(A)=merge(Ifwt(A0)+Ifwt(A1)2,Ifwt(A0)Ifwt(A1)2)

代码长得有点像 FFT。

void Xor(int *a,int tp)
{
    for(int len=1;len<(1<<n);len<<=1)
        for(int i=0;i<(1<<n);i+=(len<<1))
            for(int j=0;j<len;j++)
            {
                int x=a[i+j],y=a[i+j+len];
                a[i+j]=(x+y)*tp%mod,a[i+j+len]=(x-y+mod)%mod*tp%mod;
            }
}

一些神秘优化

Fan facts:len 可以倒过来枚举,不影响答案。
据说会变快,但试了下好像并没有什么效果,不知道是不是我的问题。


于是这篇昨天晚上就说要写的博客咕到了现在。我是摆怪猫猫!

posted @   樱雪喵  阅读(413)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示