快速沃尔什变换(FWT)

FWT 是什么

FWT 是一个用来快速求出下标进行位运算卷积的方法。

FWT 怎么实现

FFT 的快速是怎么个快呢?它是把多项式转成点值表示法,再直接相乘,又换回来系数表示法。

类似的,这时我们只需要找到一个变换 T T T,对于所有的卷积,都有
T ( A ⋅ B ) = T ( A ) ⋅ T ( B ) T(A\cdot B)=T(A)\cdot T(B) T(AB)=T(A)T(B)
I T ( T ( A ⋅ B ) ) = A ⋅ B IT(T(A\cdot B))=A\cdot B IT(T(AB))=AB

其中第一条式子后面的点乘为同次系数相乘, I T IT IT 表示 T T T 的逆变换。

下面简单介绍或、与和异或这三种常用卷积的 FWT 方法。

或卷积

想办法构造一个变换 T T T,满足上述条件。

默认 n n n 2 2 2 的幂。

以下记 [ i ∣ j = j ] [i|j=j] [ij=j] i ⊆ j i\subseteq j ij

多项式 A A A 经过变换后是一个数组。

记变换后的 A A A 的第 k k k T ( A ) k = ∑ j ⊆ k A j T(A)_k=\sum\limits_{j\sube k}A_j T(A)k=jkAj

T ( A ⋅ B ) p = ∑ i ⊆ p ∑ j ∣ k = i A j B k = ∑ ( j ∣ k ) ⊆ p A j b k T(A\cdot B)_p=\sum\limits_{i\sube p}\sum\limits_{j|k=i}A_jB_k=\sum\limits_{(j|k)\sube p}A_jb_k T(AB)p=ipjk=iAjBk=(jk)pAjbk

这时有个命题: ( i ∣ j ) ⊆ p    ⟺    ( i ⊆ p ) ∧ ( j ⊆ p ) (i|j)\sube p\iff (i\sube p)\land(j\sube p) (ij)p(ip)(jp)

怎么证明呢?

首先看 i ⊆ j i\sube j ij 意味着什么?说明 i i i 的二进制位的位置是被 j j j 的二进制位的位置包含的(这个解释是不是很像集合的用语?所以我才把它记作 i ⊆ j i\sube j ij)。

( i ∣ j ) ⊆ p (i|j)\sube p (ij)p 显然说明 ( i ⊆ p ) ∧ ( j ⊆ p ) (i\sube p)\land(j\sube p) (ip)(jp),反过来是否成立?

i ∣ j i|j ij 的二进制位为 1 1 1 的位置不可能凭空产生,一定是 i i i j j j 有。如果 i ∣ j ⊈ p i|j\not\sube p ijp,由上面的命题 i i i j j j 至少有一个不含于 p p p,矛盾。于是得证。

则有
T ( A ⋅ B ) p = ∑ ( j ∣ k ) ⊆ p A j B k = ∑ ( j ⊆ p ) ∧ ( k ⊆ p ) A j B k = ( ∑ j ⊆ p A j ) ( ∑ k ⊆ p B k ) = T ( A ) p ⋅ T ( B ) p \begin{aligned} T(A\cdot B)_p&=\sum\limits_{(j|k)\sube p}A_jB_k\\ &=\sum\limits_{(j\sube p)\land(k\sube p)}A_jB_k\\ &=\left(\sum\limits_{j\sube p}A_j\right)\left(\sum\limits_{k\sube p}B_k\right)\\ &=T(A)_p\cdot T(B)_p \end{aligned} T(AB)p=(jk)pAjBk=(jp)(kp)AjBk=(jpAj) kpBk =T(A)pT(B)p

好了,构造出一个合适的变换 T T T 了,如何快速求它呢,就像 FFT 一样?

同样采用分治的方法。 T ( A ) T(A) T(A) n n n 项,一开始是一整个块,每次分治把每个整块分成两块。

对于一个长度大于 1 1 1 的块 B B B,设其长度为 m m m,左边的块为 B 0 B_0 B0,右边的块为 B 1 B_1 B1

对于在块 B B B 的下标 x 0 x_0 x0,和 x 0 + m 2 x_0+\frac m2 x0+2m 相比,二进制位只有一位不同。

观察 T ( B ) x 0 T(B)_{x_0} T(B)x0 T ( B ) x 0 + m 2 T(B)_{x_0+\frac m2} T(B)x0+2m 的关系。

T ( A ) x 0 = ∑ j ⊆ x 0 A j T(A)_{x_0}=\sum\limits_{j\sube x_0}A_j T(A)x0=jx0Aj
T ( A ) x 0 + m 2 = ∑ j ⊆ ( x 0 + m 2 ) A j T(A)_{x_0+\frac m2}=\sum\limits_{j\sube(x_0+\frac m2)}A_j T(A)x0+2m=j(x0+2m)Aj

容易得到 ∀ j ⊆ x 0 \forall j\sube x_0 jx0,有 j ⊆ ( x 0 + m 2 ) j\sube(x_0+\frac m2) j(x0+2m)

意思是 T ( A ) x 0 T(A)_{x_0} T(A)x0 的贡献可以加到 T ( A ) x 0 + m 2 T(A)_{x_0+\frac m2} T(A)x0+2m 里。

这时可以得出一个式子:
T ( A ) = merge ⁡ ( T ( A 0 ) , T ( A 1 ) + T ( A 0 ) ) T(A)=\operatorname{merge}(T(A_0),T(A_1)+T(A_0)) T(A)=merge(T(A0),T(A1)+T(A0))

其中 merge ⁡ \operatorname{merge} merge 表示数组拼接。

逆变换的分治的式子如下
I T ( A ) = merge ⁡ ( I T ( A 0 ) , I T ( A 1 ) − I T ( A 0 ) ) IT(A)=\operatorname{merge}(IT(A_0),IT(A_1)-IT(A_0)) IT(A)=merge(IT(A0),IT(A1)IT(A0))

感性理解,正变换在原来的基础上加上了 T ( A 0 ) T(A_0) T(A0),逆变换时就要减掉 I T ( A 0 ) IT(A_0) IT(A0)

下面是代码实现

void fwt_or(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                a[k+i/2]=(a[k+i/2]+fl*a[k]+mod)%mod;
            }
        }
    }
}

与卷积

和或卷积类似,构造变换 T T T

上面或卷积构造 T T T 的方法实际上是利用了 ( i ∣ j ) ⊆ p    ⟺    ( i ⊆ p ) ∧ ( j ⊆ p ) (i|j)\sube p\iff (i\sube p)\land(j\sube p) (ij)p(ip)(jp) 的性质。

这里也要弄出类似的命题。

[ i & j = i ] [i\&j=i] [i&j=i] i ⊆ j i\sube j ij

p ⊆ ( i & j )    ⟺    ( p ⊆ i ) ∧ ( p ⊆ j ) p\sube(i\&j)\iff(p\sube i)\land(p\sube j) p(i&j)(pi)(pj)

所以设 T ( A ) p = ∑ p ⊆ i A i T(A)_p=\sum\limits_{p\sube i}A_i T(A)p=piAi

可得
T ( A ⋅ B ) p = ∑ p ⊆ ( i & j ) A i B j = ∑ ( p ⊆ i ) ∧ ( p ⊆ j ) A i B j = ( ∑ p ⊆ i A i ) ( ∑ p ⊆ j B j ) = T ( A ) p ⋅ T ( B ) p \begin{aligned} T(A\cdot B)_p&=\sum\limits_{p\sube(i\&j)}A_iB_j\\ &=\sum\limits_{(p\sube i)\land(p\sube j)}A_iB_j\\ &=\left(\sum\limits_{p\sube i}A_i\right)\left(\sum\limits_{p\sube j}B_j\right)\\ &=T(A)_p\cdot T(B)_p \end{aligned} T(AB)p=p(i&j)AiBj=(pi)(pj)AiBj=(piAi)(pjBj)=T(A)pT(B)p

要求 T ( A ) T(A) T(A) 同样分治,直接给出式子。
T ( A ) = merge ⁡ ( T ( A 0 ) + T ( A 1 ) , T ( A 1 ) ) T(A)=\operatorname{merge}(T(A_0)+T(A_1),T(A_1)) T(A)=merge(T(A0)+T(A1),T(A1))

符号和字母意义同上。

这里的式子和上面或卷积的不一样。这里是由后面贡献前面,为什么呢?

因为对于一个大小大于一的块 B B B,长度为 m m m,如果 x 0 x_0 x0 是块 B B B 里的下标,一定满足 x 0 ⊆ ( x 0 + m 2 ) x_0\sube(x_0+\frac m2) x0(x0+2m),所以只能用右边给左边做贡献。

至于逆变换也和上面差不多,为
I T ( A ) = merge ⁡ ( I T ( A 0 ) − I T ( A 1 ) , I T ( A 1 ) ) IT(A)=\operatorname{merge}(IT(A_0)-IT(A_1),IT(A_1)) IT(A)=merge(IT(A0)IT(A1),IT(A1))

代码实现如下

void fwt_and(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                a[k]=(a[k]+fl*a[k+i/2]+mod)%mod;
            }
        }
    }
}

异或卷积

到了异或,情况就比上面复杂多了。

首先得知道一个性质,设 d n d_n dn n n n 的二进制中 1 1 1 的个数的奇偶性。

则有 d n ⊕ d m = d n ⊕ m d_n\oplus d_m=d_{n\oplus m} dndm=dnm

证明如下

等号左边代表 n , m n,m n,m 二进制 1 1 1 的个数之和的奇偶性。
异或操作里面对于每一位如果都是 1 1 1 结果为 0 0 0,减少的 1 1 1 是两个,而其他情况 1 1 1 的数量是不会减少。因为减少两个 1 1 1 不会改变奇偶性,得证。

根据这个结论,可推出
d i & p ⊕ d j & p = d ( i & p ) ⊕ ( j & p ) = d ( i ⊕ j ) & p \begin{aligned} d_{i\&p}\oplus d_{j\&p}&=d_{(i\&p)\oplus(j\&p)}\\ &= d_{(i\oplus j)\&p} \end{aligned} di&pdj&p=d(i&p)(j&p)=d(ij)&p

有什么式子的值会随着奇偶性的变化而变化呢?就是 ( − 1 ) n (-1)^n (1)n

所以令 T ( A ) p = ∑ i = 0 n − 1 ( − 1 ) d i & p A i T(A)_p=\sum\limits_{i=0}^{n-1}(-1)^{d_{i\&p}}A_i T(A)p=i=0n1(1)di&pAi

则有
T ( A ⋅ B ) p = ∑ i = 0 n − 1 ( − 1 ) d ( j ⊕ k ) & p A j B k = ∑ i = 0 n − 1 ( − 1 ) d j & p ⊕ d k & p A j B k = ( ∑ i = 0 n − 1 ( − 1 ) d j & p A j ) ( ∑ i = 0 n − 1 ( − 1 ) d k & p B k ) = T ( A ) p ⋅ T ( B ) p \begin{aligned} T(A\cdot B)_p&=\sum\limits_{i=0}^{n-1}(-1)^{d_{(j\oplus k)\& p}}A_jB_k\\ &=\sum\limits_{i=0}^{n-1}(-1)^{d_{j\&p}\oplus d_{k\&p}}A_jB_k\\ &=\left(\sum\limits_{i=0}^{n-1}(-1)^{d_{j\& p}}A_j\right)\left(\sum\limits_{i=0}^{n-1}(-1)^{d_{k\&p}}B_k\right)\\ &=T(A)_p\cdot T(B)_p \end{aligned} T(AB)p=i=0n1(1)d(jk)&pAjBk=i=0n1(1)dj&pdk&pAjBk=(i=0n1(1)dj&pAj)(i=0n1(1)dk&pBk)=T(A)pT(B)p

分治求出 T ( A ) T(A) T(A) 的式子如下:
T ( A ) = merge ⁡ ( T ( A 0 ) + T ( A 1 ) , T ( A 0 ) − T ( A 1 ) ) T(A)=\operatorname{merge}(T(A_0)+T(A_1),T(A_0)-T(A_1)) T(A)=merge(T(A0)+T(A1),T(A0)T(A1))

怎么证明呢?

考虑较小的前 i i i 位,不考虑其他位。

分成的两个小块对应的位, p p p 只有第 ( i − 1 ) (i-1) (i1) 位才会改变。

对于前半部分 p p p 的那一位为 0 0 0,有 0 & 0 = 0 , 0 & 1 = 0 0\&0=0,0\&1=0 0&0=0,0&1=0,发现结果都是 0 0 0 不会有影响,所以都加起来。

对于后半部分 p p p 的那一位为 1 1 1,有 1 & 0 = 0 , 1 & 1 = 1 1\&0=0,1\&1=1 1&0=0,1&1=1,前半部分为 0 0 0 不变,后半部分为 1 1 1,所以贡献会乘上 − 1 -1 1

而逆变换就是反过来
I T ( A ) = merge ⁡ ( I T ( A 0 ) + I T ( A 1 ) 2 , I T ( A 0 ) − I T ( A 1 ) 2 ) IT(A)=\operatorname{merge}\left(\frac{IT(A_0)+IT(A_1)}2,\frac{IT(A_0)-IT(A_1)}2\right) IT(A)=merge(2IT(A0)+IT(A1),2IT(A0)IT(A1))

手动验证一下

I T ( A 0 ) + I T ( A 1 ) 2 + I T ( A 0 ) − I T ( A 1 ) 2 = I T ( A 0 ) \dfrac{IT(A_0)+IT(A_1)}2+\dfrac{IT(A_0)-IT(A_1)}2=IT(A_0) 2IT(A0)+IT(A1)+2IT(A0)IT(A1)=IT(A0)

I T ( A 0 ) + I T ( A 1 ) 2 − I T ( A 0 ) − I T ( A 1 ) 2 = I T ( A 1 ) \dfrac{IT(A_0)+IT(A_1)}2-\dfrac{IT(A_0)-IT(A_1)}2=IT(A_1) 2IT(A0)+IT(A1)2IT(A0)IT(A1)=IT(A1)

结果是正确的。

代码实现如下

void fwt_xor(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                ll x=a[k]+a[k+i/2],y=a[k]-a[k+i/2]+mod;
                a[k]=x*(fl==1?1:inv2)%mod,a[k+i/2]=y*(fl==1?1:inv2)%mod;
            }
        }
    }
}

例题

P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)

参考代码如下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=17;
const ll mod=998244353,inv2=499122177;
int n;
ll a[1<<N],b[1<<N],A[1<<N],B[1<<N];
void fwt_or(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                a[k+i/2]=(a[k+i/2]+fl*a[k]+mod)%mod;
            }
        }
    }
}
void fwt_and(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                a[k]=(a[k]+fl*a[k+i/2]+mod)%mod;
            }
        }
    }
}
void fwt_xor(ll a[],ll fl)
{
    for(int i=2;i<=n;i<<=1){
        for(int j=0;j<n;j+=i){
            for(int k=j;k<j+i/2;k++){
                ll x=a[k]+a[k+i/2],y=a[k]-a[k+i/2]+mod;
                a[k]=x*(fl==1?1:inv2)%mod,a[k+i/2]=y*(fl==1?1:inv2)%mod;
            }
        }
    }
}
int main()
{
    scanf("%d",&n);
    n=1<<n;
    for(int i=0;i<n;i++) scanf("%lld",&a[i]);
    for(int i=0;i<n;i++) scanf("%lld",&b[i]);
    for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
    fwt_or(A,1),fwt_or(B,1);
    for(int i=0;i<n;i++) A[i]=A[i]*B[i]%mod;
    fwt_or(A,-1);
    for(int i=0;i<n;i++) printf("%lld ",A[i]);
    puts("");
    for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
    fwt_and(A,1),fwt_and(B,1);
    for(int i=0;i<n;i++) A[i]=A[i]*B[i]%mod;
    fwt_and(A,-1);
    for(int i=0;i<n;i++) printf("%lld ",A[i]);
    puts("");
    for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
    fwt_xor(A,1),fwt_xor(B,1);
    for(int i=0;i<n;i++) A[i]=A[i]*B[i]%mod;
    fwt_xor(A,-1);
    for(int i=0;i<n;i++) printf("%lld ",A[i]);
}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值