卷积全家桶

你可能会问为什么这里只有 FWT,因为 FFT 和 NTT 我懒得写,好了,FFT 和 NTT 在写,2~3 天后补上。

快速沃尔什变换(FWT)

变换内容

快速沃尔什变换(下称 FWT)通常用来解决的问题形如

(1)Ck=ij=kAiBj

其中, 是一种二元运算。与 FFT 中的加法不同, 一般是位运算,它可以按位计算

显然,对于数组(向量)X,我们需要构造 FWT(X)(它也是一个数组),使得对于任意的 i,满足

(2)FWT(C)i=FWT(A)iFWT(B)i

这样,我们可以通过求出 FWT(A)FWT(B) 来求出 FWT(C),再通过逆 FWT 来求出 C

那么,如何构造 FWT(X) 呢?一种显然的构造是

(3)FWT(X)i=jci,jXj

上述构造意在通过 ci,j(ci,j=0/1) 来建立起来 ij 的联系,我们将它带回 (2) 中,这样,对于任意的 i,有

(4)jci,jCj=(jci,jAj)(jci,jBj)

进一步地,有

(5)jci,jCj=k,lci,kci,lAkBl

(1),有

(6)jci,jCj=jci,jkl=jAkBl=jkl=jci,klAkBl=k,lci,klAkBl

(5) 联立,容易发现可以有

(7)ci,kci,l=ci,kl

好的,现在我们发现了 c 的一个重要性质!在后面将会用到。

下面,我们开始求 FWT(X)i。设 X 的长度为 n,记 x(j) 表示 x 在二进制下从低往高第 j 位的值,设 kn 在二进制下的最高位,记 i,j 表示分别去掉 i,j 的第 k 位后的值。根据 (3),将 FWT(X)i 分成两半,有

(8)FWT(X)i=jci,jXj=j=0(n/2)1ci,jXj+j=(n/2)n1ci,jXj

分开考虑它们下标(就是 j)的(二进制)第 k 位,显然,前半部分的是 0,后半部分的是 1,再根据 的性质和 (7),有

(9)FWT(X)i=j=0(n/2)1ci,jXj+j=(n/2)n1ci,jXj=ci(k),0j=0(n/2)1ci,jXj+ci(k),1j=(n/2)n1ci,jXj

这里,对于 (n/2)j<n,有 ci,j=ci,j,而且对于 0j<(n/2),有 j=j,那么有

(10)FWT(X)i=ci(k),0j=0(n/2)1ci,jXj+ci(k),1j=(n/2)n1ci,jXj=ci(k),0j=0(n/2)1ci,jXj+ci(k),1j=(n/2)n1ci,jXj

其实,两个 每个都可以看作一个子问题。记 X0X 中下标在 0(n/2)1 范围内的数成的数组,X1 表是 X 中下标在 (n/2)n1 范围内的数成的数组(在 X1 中起始位置为 0)。这样分割开 X 后,对于在 X 中的下标 i,它在 X1 中的位置为 i(n/2),即 ii,那么有

(11)FWT(X)i=ci(k),0j=0(n/2)1ci,jXj+ci(k),1j=(n/2)n1ci,jXj=ci(k),0FWT(X0)i+ci(k),1FWT(X1)i=ci(k),0FWT(X0)i+ci(k),1FWT(X1)i

下面我们将对 i(k) 分类讨论。若 i(k)=0,有

(12)FWT(X)i=c0,0FWT(X0)i+c0,1FWT(X1)i

否则,有

(13)FWT(X)i=c1,0FWT(X0)i+c1,1FWT(X1)i

尝试去掉 i。记 FWT(X) 的前半部分(即 i(k)=0 的部分)记为 FWT0(X),后半部分记为 FWT1(X),那么 FWT(X) 为它们首尾拼接起来,此时有

(14)FWT0(X)=c0,0FWT(X0)+c0,1FWT(X1)

(15)FWT1(X)=c1,0FWT(X0)+c1,1FWT(X1)

这里的加号表示数组对位相加,乘号表示数组中所有数都乘上一个数,下同。

综合以上,我们成功地将 FWT(X) 分拆成了两个大小相同的子问题。这样,我们可以通过分治在 O(nlogn) 的时间复杂度内求出 FWT(X)

等等!还没完!我们还只是通过 (2) 求出了 FWT(C),那么如何求出 C 呢?下面我们将介绍逆 FWT。

IFWT(X) 表示逆 FWT(IFWT0/1(X) 的定义同 FWT0/1(X)),对于数 abIFWT(X)FWT(X) 有性质

(16)IFWT(FWT(X))=FWT(IFWT(X))

(17)FWT(aX+bY)=aFWT(X)+bFWT(Y)

这两个性质都可以用 (3) 的定义式来证明,结合它们,有

(18)IFWT(aX+bY)=aIFWT(aX)+bIFWT(bY)

仔细观察 (14)(15),你会发现这酷似一个矩阵乘法的式子,FWT(X0) 在矩阵中可以看做一个行向量,有

(19)[c0,0c0,1c1,0c1,1]×[FWT(X0)FWT(X1)]=[FWT0(X)FWT1(X)]

我们先记录下它左边的式子,有

(20)T=[c0,0c0,1c1,0c1,1]

T1T 的逆矩阵,那么,如果 (16) 两边同时左乘 T1,有

(21)[FWT(X0)FWT(X1)]=T1×[FWT0(X)FWT1(X)]

(22)T1=[t0,0,t0,1t1,0,t1,1]

回到 (21),令 X=IFWT(X),有

(23)[FWT(IFWT0(X))FWT(IFWT1(X))]=[t0,0,t0,1t1,0,t1,1]×[FWT0(IFWT(X))FWT1(IFWT(X))]

(24)[FWT(IFWT0(X))FWT(IFWT1(X))]=[t0,0,t0,1t1,0,t1,1]×[X0X1]

那么有

(25)FWT(IFWT0(X))=t0,0X0+t0,1X1

(26)FWT(IFWT1(X))=t1,0X0+t1,1X1

先只考虑 (25),两边同时做逆 FWT,有

(27)IFWT0(X)=IFWT(t0,0X0+t0,1X1)

(28)IFWT0(X)=t0,0IFWT(X0)+t0,1IFWT(X1)

同理,对于 (26),有

(29)IFWT1(X)=t1,0IFWT(X0)+t1,1IFWT(X1)

这样,我们只用拼接 IFWT0(X)IFWT1(X) 即可。

观察 FWT 的 (14)(15) 和逆 FWT 的 (28)(29),你会发现它们很像,只有系数上的区别,所以我们可以将 FWT 和逆 FWT 写在一个函数内。

下面,让我们来讨论几组常见的卷积。记 表示按位或运算, 表示按位与运算, 表示按位异或运算。

一些常见卷积

按位或

此时 =or,那么我们有构造

(30)T=[1011]

显然,它满足 (7),即

(31)ci,kci,l=ci,kl

那么有

(32)FWT0(X)=FWT(X0)

(33)FWT1(X)=FWT(X0)+FWT(X1)

(34)FWT(X)=merge(FWT(X0),FWT(X0)+FWT(X1))

这里所有的定义和前面一样,其中 merge(U,V) 表示将数组 U,V 首尾拼接成一个新数组。

现在我们需要做逆 FWT 变换,有

(35)T1=[1011]

那么有

(36)IFWT0(X)=IFWT(X0)

(37)IFWT1(X)=IFWT(X1)IFWT(X0)

(38)IFWT(X)=merge(IFWT(X0),IFWT(X1)IFWT(X0))

按位与

有构造

(39)T=[1101],T1=[1101]

按位异或

有构造

(40)T=[1111],T1=[0.50.50.50.5]

在这里,你需要使用乘法逆元来计算 0.5

具体实现

参考代码:

void g_or(int *f, int tp) {
    for (int x = 2; x <= n; x <<= 1) {
        int k = x >> 1;
        for (int i = 0; i < n; i += x) {
            for (int j = 0; j < k; j++) {
                f[i + j + k] = (f[i + j + k] + f[i + j] * tp + P) % P;
            }
        }
    }
}
void g_and(int *f, int tp) {
    for (int x = 2; x <= n; x <<= 1) {
        int k = x >> 1;
        for (int i = 0; i < n; i += x) {
            for (int j = 0; j < k; j++) {
                f[i + j] = (f[i + j] + f[i + j + k] * tp + P) % P;
            }
        }
    }
}
void g_xor(int *f, int tp) {
    for (int x = 2; x <= n; x <<= 1) {
        int k = x >> 1;
        for (int i = 0; i < n; i += x) {
            for (int j = 0; j < k; j++) {
                f[i + j] = (f[i + j] + f[i + j + k]) % P;
                f[i + j + k] = (f[i + j] - (2 * f[i + j + k] % P) + P) % P;
                f[i + j] = f[i + j] * tp % P;
                f[i + j + k] = f[i + j + k] * tp % P;
            }
        }
    }
}

未完待续...

posted @   Eliauk_FP  阅读(14)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示