快速沃尔什变换 FWT 学习笔记【多项式】

〇、前言

欢迎到我的 Wordpress 博客查看

之前看到异或就担心是 FWT,然后才开始想别的。

这次学了 FWT 以后,以后判断应该就很快了吧?

参考资料

一、FWT 是什么

FWT 是快速沃尔什变换。它和快速傅里叶变换一样,原本都用于物理中的频谱分析。

但是由于它可分治的特点,在算法竞赛中常被用来计算位运算卷积。

二、FWT 能干什么

它可以在 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的时间复杂度内由数组 a , b a,b a,b 得到数组 c c c,满足
∀ i ∈ [ 0 , n )   c i = ∑ j ⊕ k = i a j × b k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } \forall i\in[0,n) \ c_i=\sum_{j\oplus k=i}a_j\times b_k i[0,n) ci=jk=iaj×bk
其中 ⊕ \oplus 可以代表“与”,“或”,“异或”中的任意一种运算。

这叫做位运算卷积。

三、与、或卷积

我们需要把 a , b a,b a,b 数组分别转化为 a ′ , b ′ a',b' a,b 来通过一次乘法解决多个乘法问题。

对于或,我们有:若 j   o r   i = i , k   o r   i = i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }j\or i=i,k\or i=i j or i=i,k or i=i ( j   o r   k )   o r   i = i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(j\or k)\or i=i (j or k) or i=i

虽然这样看上去和题目要求还差了一点,但是我们如果这样想呢:

构造数组 a ′ , b ′ a',b' a,b
a i ′ = ∑ j   o r   i = i a j b i ′ = ∑ k   o r   i = i b k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } a'_i=\sum_{j\or i=i}a_j\\ b'_i=\sum_{k\or i=i}b_k ai=j or i=iajbi=k or i=ibk
即通过正变换 a a a 转化为 a ′ a' a,由 b b b 转化为 b ′ b' b

那么
c i ′ = a i ′ × b i ′ = ∑ j   o r   i = i a j ∑ k   o r   i = i b k = ∑ j   o r   i = i ∑ k   o r   i = i a j b k = ∑ ( j   o r   k )   o r   i = i a j b k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } \begin{aligned} c'_i&=a'_i\times b'_i\\ &=\sum_{j\or i=i}a_j\sum_{k\or i=i}b_k\\ &=\sum_{j\or i=i}\sum_{k\or i=i}a_jb_k\\ &=\sum_{(j\or k)\or i=i}a_jb_k \end{aligned} ci=ai×bi=j or i=iajk or i=ibk=j or i=ik or i=iajbk=(j or k) or i=iajbk
( j   o r   k )   o r   i = i \newcommand{\or}{\ \mathrm{or}\ }(j\or k)\or i=i (j or k) or i=i 就又是变换完的形式了。

再通过逆变换 c ′ c' c 转化回 c c c,那么 c c c 就是满足 c i = ∑ j   o r   k = i a j b k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }c_i=\sum_{j\or k=i}a_jb_k ci=j or k=iajbk 的结果了。

同理,由于与运算满足:若 j   a n d   i = i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }j\and i=i j and i=i k   a n d   i = i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }k\and i=i k and i=i,则 ( j   a n d   k )   a n d   i = i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(j\and k)\and i=i (j and k) and i=i

因此和上面的变换是一样的。

现在我们需要找出 a → a ′ a\to a' aa 是怎么实现的。

正变换

针对或变换的举例:
∀ i ∈ [ 0 , n )   a i ′ = ∑ j   o r   i = i a i \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } \forall i\in [0,n)\ a'_i=\sum_{j\or i=i}a_i i[0,n) ai=j or i=iai
我们可以按位分治。从下到上转移,第 i i i 层的状态 j j j f [ i , j ] f[i,j] f[i,j] 表示所有比 i i i 高的位与 j j j 相同的状态 k k k 的和。即
f [ i , j ] = ∑ ⌊ k 2 i ⌋ = ⌊ j 2 i ⌋ , k   o r   j = j a k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } f[i,j]=\sum_{\left\lfloor\frac{k}{2^i}\right\rfloor=\left\lfloor\frac{j}{2^i}\right\rfloor,k\or j=j}a_k f[i,j]=2ik=2ij,k or j=jak
其中 ⌊ k 2 i ⌋ \left\lfloor\frac{k}{2^i}\right\rfloor 2ik 表示将 k k k 在二进制下右移 i i i 位。

如果还不好理解,那么对于 f [ 5 , 101100111 0 ( 2 ) ] f[5,1011001110_{(2)}] f[5,1011001110(2)],满足条件的 k k k
i = 5 j = 1011001110 k = 1011000000   o r   x \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } i=5\\ \begin{aligned} j&=1011001110\\ k&=1011000000\or x \end{aligned} i=5jk=1011001110=1011000000 or x
其中的 x x x 满足 x   o r   001110 = 001110 \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }x\or 001110=001110 x or 001110=001110 k k k 必须满足在第 5 ∼ 9 5\sim 9 59 位与 j j j 相同。

分析方程,会发现我们是可以利用 f [ i − 1 ] f[i-1] f[i1] 的信息的。在 f [ i − 1 , j ] f[i-1,j] f[i1,j] 中的每一个状态所存的 ∑ a k \sum a_k ak j j j k k k 从第 i i i 位到最高位都是相等的,现在我们用到了第 i i i 位,那么就考虑第 i i i 位的取值。

就有了简洁的状态转移,令 j j j 的第 i i i 位是 0 0 0
f [ i , j ] = f [ i − 1 , j ] , f [ i , j + 2 i ] = f [ i − 1 , j ] + f [ i − 1 , j + 2 i ] \begin{aligned} f[i,j]&=f[i-1,j],\\ f[i,j+2^i]&=f[i-1,j]+f[i-1,j+2^i] \end{aligned} f[i,j]f[i,j+2i]=f[i1,j],=f[i1,j]+f[i1,j+2i]
所以 a ′ = f [ ⌈ log ⁡ n ⌉ ] a'=f[\left\lceil\log n\right\rceil] a=f[logn],答案就是最上面一层。

同理,与的正变换的方程恰好反过来了
f [ i , j ] = f [ i − 1 , j ] + f [ i − 1 , j + 2 i ] , f [ i , j + 2 i ] = f [ i − 1 , j + 2 i ] \begin{aligned} f[i,j]&=f[i-1,j]+f[i-1,j+2^i],\\ f[i,j+2^i]&=f[i-1,j+2^i] \end{aligned} f[i,j]f[i,j+2i]=f[i1,j]+f[i1,j+2i],=f[i1,j+2i]

逆变换

逆变换是由 f [ i ] f[i] f[i] f [ i − 1 ] f[i-1] f[i1] 的过程。

直接由上面的式子倒过来就可以了。

或:
f [ i , j ] = f [ i + 1 , j ] , f [ i , j + 2 i ] = f [ i + 1 , j + 2 i ] − f [ i + 1 , j ] \begin{aligned} f[i,j]&=f[i+1,j],\\ f[i,j+2^i]&=f[i+1,j+2^i]-f[i+1,j] \end{aligned} f[i,j]f[i,j+2i]=f[i+1,j],=f[i+1,j+2i]f[i+1,j]
与:
f [ i , j ] = f [ i + 1 , j ] − f [ i + 1 , j + 2 i ] , f [ i , j + 2 i ] = f [ i + 1 , j + 2 i ] \begin{aligned} f[i,j]&=f[i+1,j]-f[i+1,j+2^i],\\ f[i,j+2^i]&=f[i+1,j+2^i] \end{aligned} f[i,j]f[i,j+2i]=f[i+1,j]f[i+1,j+2i],=f[i+1,j+2i]
因此卷积的答案最后就存在 f [ 1 , i ] = ∑ j ⊕ k = i a j b k f[1,i]=\sum_{j\oplus k=i}a_jb_k f[1,i]=jk=iajbk 里了。

四、异或卷积

这个东西有点麻烦,仍然需要构造。

定义运算 x ⊗ y = popcount ⁡ ( x   a n d   y )   m o d   2 \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }x\otimes y=\operatorname{popcount}(x\and y)\bmod 2 xy=popcount(x and y)mod2,称之为 x x x y y y 的奇偶性。

它是一个满足 ( i ⊗ j )   x o r   ( i ⊗ k ) = i ⊗ ( j   x o r   k ) \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(i\otimes j)\xor (i\otimes k)=i\otimes(j\xor k) (ij) xor (ik)=i(j xor k) 的运算,所以可以用来做异或卷积。

构造
a i ′ = ∑ i ⊗ j = 0 a j − ∑ i ⊗ j = 1 a j b i ′ = ∑ i ⊗ k = 0 b k − ∑ i ⊗ k = 1 b k a'_i=\sum_{i\otimes j=0}a_j-\sum_{i\otimes j=1}a_j\\ b'_i=\sum_{i\otimes k=0}b_k-\sum_{i\otimes k=1}b_k\\ ai=ij=0ajij=1ajbi=ik=0bkik=1bk

c i ′ = ∑ i ⊗ j = 0 a j ∑ i ⊗ k = 0 b k − ∑ i ⊗ j = 0 a j ∑ i ⊗ k = 1 b k − ∑ i ⊗ j = 1 a j ∑ i ⊗ k = 0 b k + ∑ i ⊗ j = 1 a j ∑ i ⊗ k = 1 b k = ∑ i ⊗ ( j   x o r   k ) = 0 a j b k − ∑ i ⊗ ( j   x o r   k ) = 1 a j b k \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ } \begin{aligned} c'_i&=\sum_{i\otimes j=0}a_j\sum_{i\otimes k=0}b_k-\sum_{i\otimes j=0}a_j\sum_{i\otimes k=1}b_k-\sum_{i\otimes j=1}a_j\sum_{i\otimes k=0}b_k+\sum_{i\otimes j=1}a_j\sum_{i\otimes k=1}b_k\\ &=\sum_{i\otimes(j\xor k)=0}a_jb_k-\sum_{i\otimes(j\xor k)=1}a_jb_k \end{aligned} ci=ij=0ajik=0bkij=0ajik=1bkij=1ajik=0bk+ij=1ajik=1bk=i(j xor k)=0ajbki(j xor k)=1ajbk
解释:式子中的第一行,第一项和第四项构成了 i ⊗ ( j   x o r   k ) = 0 \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }i\otimes(j\xor k)=0 i(j xor k)=0 的全部可能性: 00 00 00 11 11 11;第二项和第三项构成了 i ⊗ ( j   x o r   k ) = 1 \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }i\otimes(j\xor k)=1 i(j xor k)=1 的全部可能性: 01 01 01 10 10 10。所以可以写 ∑ \sum ,而且由于每项不相交,所以不能乘 2 2 2

可以发现 c ′ c' c 也是一个变换完了的式子,把它逆变换回去就可以了。

正变换

仍然按位分治,同样考虑上面那样逐位转移。

在枚举第 i i i 位的不同时,状态 j j j 和状态 j + 2 i j+2^i j+2i 都可以从第 i − 1 i-1 i1 层的 j j j j + 2 i j+2^i j+2i 转移过来。其中 j j j 的第 i i i 位为 0 0 0

这样的话有四种情况:

  • [ i , j ] ← [ i − 1 , j ] [i,j]\leftarrow[i-1,j] [i,j][i1,j] ( 0   a n d   0 ) \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(0\and 0) (0 and 0) 是不变的;
  • [ i , j ] ← [ i − 1 , j + 2 i ] [i,j]\leftarrow[i-1,j+2^i] [i,j][i1,j+2i] ( 0   a n d   1 ) \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(0\and 1) (0 and 1) 是不变的;
  • [ i , j + 2 i ] ← [ i − 1 , j ] [i,j+2^i]\leftarrow[i-1,j] [i,j+2i][i1,j] ( 1   a n d   0 ) \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(1\and 0) (1 and 0) 是不变的;
  • [ i , j + 2 i ] ← [ i − 1 , j + 2 i ] [i,j+2^i]\leftarrow[i-1,j+2^i] [i,j+2i][i1,j+2i] ( 1   a n d   1 ) \newcommand{\and}{\ \mathrm{and}\ } \newcommand{\or}{\ \mathrm{or}\ } \newcommand{\xor}{\ \mathrm{xor}\ }(1\and 1) (1 and 1) 会改变。

这个图中蓝色(无色)的箭头表示正转移,其他颜色的箭头表示负转移。

也就是说,转移之后,这个状态内部的全部元素进行 ⊗ \otimes 的结果都从 0 0 0 变成了 1 1 1 或从 1 1 1 变成了 0 0 0

那么在最终结果方面就会产生影响,因此那些转移我们把它定为负的。

还有一种理解方法。因为最上面一行是我们正变换的结果,可以通过这个图从上到下来看出它的贡献来源。

a i ′ a'_i ai 出发,遇到有颜色的边,就要把子数内的贡献取反( × − 1 \times -1 ×1),它的意义也是 k ⊗ i = ¬ ( k ⊗ i ) k\otimes i=\neg(k\otimes i) ki=¬(ki)

这样对每一个位置就可以满足
f [ i , j ] = ∑ k ⊗ j = 0 a k − ∑ k ⊗ j = 1 a k f[i,j]=\sum_{k\otimes j=0}a_k-\sum_{k\otimes j=1}a_k f[i,j]=kj=0akkj=1ak
了。其中 k k k 只枚举了有效位。

观察图可以发现,状态转移方程是
f [ i , j ] = f [ i − 1 , j ] + f [ i − 1 , j + 2 i ] , f [ i , j + 2 i ] = f [ i − 1 , j ] − f [ i − 1 , j + 2 i ] \begin{aligned} f[i,j]&=f[i-1,j]+f[i-1,j+2^i],\\ f[i,j+2^i]&=f[i-1,j]-f[i-1,j+2^i] \end{aligned} f[i,j]f[i,j+2i]=f[i1,j]+f[i1,j+2i],=f[i1,j]f[i1,j+2i]

逆变换

把正变换上下相减,除以 2 2 2 即可
f [ i , j ] = f [ i + 1 , j ] + f [ i + 1 , j + 2 i ] 2 , f [ i , j + 2 i ] = f [ i + 1 , j ] − f [ i + 1 , f [ j + 2 i ] ] 2 \begin{aligned} f[i,j]&=\frac{f[i+1,j]+f[i+1,j+2^i]}{2},\\ f[i,j+2^i]&=\frac{f[i+1,j]-f[i+1,f[j+2^i]]}{2} \end{aligned} f[i,j]f[i,j+2i]=2f[i+1,j]+f[i+1,j+2i],=2f[i+1,j]f[i+1,f[j+2i]]

五、代码

#include<cstdio>
#include<cstring>
#define p 998244353
#define inv 499122177ll
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int read()
{
    int x=0;
    char ch=gc();
    while(ch<'0'||ch>'9')
        ch=gc();
    while(ch>='0'&&ch<='9')
    {
        x=x*10+(ch&15);
        ch=gc();
    }
    return x;
}
int A[1<<17],B[1<<17],a[1<<17],b[1<<17],n,tot;
void init()
{
    for(int i=0;i<(1<<n);++i)
    {
        a[i]=A[i];
        b[i]=B[i];
    }
}
void Or(int *f)
{
    for(int bs=2;bs<=tot;bs<<=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
                f[i+j+g]=(f[i+j]+f[i+j+g])%p;
    }
}
void iOr(int *f)
{
    for(int bs=tot;bs>=2;bs>>=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
                f[i+j+g]=(f[i+j+g]+p-f[i+j])%p;
    }
}
void And(int *f)
{
    for(int bs=2;bs<=tot;bs<<=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
                f[i+j]=(f[i+j]+f[i+j+g])%p;
    }
}
void iAnd(int *f)
{
    for(int bs=tot;bs>=2;bs>>=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
                f[i+j]=(f[i+j]+p-f[i+j+g])%p;
    }
}
void Xor(int *f)
{
    for(int bs=2;bs<=tot;bs<<=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
            {
                int t0=(f[i+j]+f[i+j+g])%p,t1=(f[i+j]+p-f[i+j+g])%p;
                f[i+j]=t0;
                f[i+j+g]=t1;
            }
    }
}
void iXor(int *f)
{
    for(int bs=tot;bs>=2;bs>>=1)
    {
        int g=(bs>>1);
        for(int i=0;i<tot;i+=bs)
            for(int j=0;j<g;++j)
            {
                int t0=inv*(f[i+j]+f[i+j+g])%p,t1=inv*(f[i+j]+p-f[i+j+g])%p;
                f[i+j]=t0;
                f[i+j+g]=t1;
            }
    }
}
int main()
{
    #ifdef wjyyy
        freopen("a.in","r",stdin);
    #endif
    n=read();
    tot=(1<<n);
    for(int i=0;i<tot;++i)
        A[i]=read();
    for(int i=0;i<tot;++i)
        B[i]=read();
    init();
    Or(a);
    Or(b);
    for(int i=0;i<tot;++i)
        a[i]=(long long)a[i]*b[i]%p;
    iOr(a);
    for(int i=0;i<tot;++i)
        printf("%d ",a[i]);
    puts("");
    init();
    And(a);
    And(b);
    for(int i=0;i<tot;++i)
        a[i]=(long long)a[i]*b[i]%p;
    iAnd(a);
    for(int i=0;i<tot;++i)
        printf("%d ",a[i]);
    puts("");
    init();
    Xor(a);
    Xor(b);
    for(int i=0;i<tot;++i)
        a[i]=(long long)a[i]*b[i]%p;
    iXor(a);
    for(int i=0;i<tot;++i)
        printf("%d ",a[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值