FWT学习笔记

引入

现在有一个长成这样子的卷积:

\[C_i=\sum_{j\oplus k=i}A_j\times B_k \]

其中 \(\oplus\) 可以是异或,与,非,序列长度都是 \(2^n\)

解决

这就需要 FWT 来解决了。

我们之前做 FFT 的时候,是把多项式转化成点值的形式,然后就可以点值相乘了,那么我们也想在 FWT 的过程中实现这个东西,设 \(A'[i]\) 表示多项式 \(A\) 转化为点值之后的第 \(i\) 个点,那么就有:

\[C'[i]=A'[i]\times B'[i] \]

我们再设 \(c(i,j)\) 表示多项式第 \(j\) 项的系数给第 \(i\) 个点的贡献,那么就有:

\[A'[i]=\sum_{j=0}^{2^n-1}A_j \]

\[C'[i]=\sum_{j=0}^{2^n-1}c(i,j)A_j\times\sum_{k=0}^{2^n-1}c(i,k)B_k=\sum_{j=0}^{2^n-1}\sum_{k=0}^{2^n-1}c(i,j)c(i,k)A_jB_k \]

而又有:

\[C_p=\sum_{t_1\oplus t_2=p}A_{t_1}\times B_{t_2} \]

\[\sum_{p=0}^{2^n-1}c(i,p)C_p=\sum_{p=0}^{2^n-1}c(i,p)\sum_{t_1\oplus t_2=p}A_{t_1}\times B_{t_2} \]

和上面的式子联立就有:

\[\sum_{j=0}^{2^n-1}\sum_{k=0}^{2^n-1}c(i,j)c(i,k)A_jB_k=\sum_{p=0}^{2^n-1}c(i,p)\sum_{t_1\oplus t_2=p}A_{t_1}\times B_{t_2}=\sum_{t_1=0}^{2^n-1}\sum_{t_2=0}^{2^n-1}c(i,t_1\oplus t_2)A_{t_1}\times B_{t_2} \]

所以就有 \(c(i,j)c(i,k)=c(i,j\oplus k)\) ,这也是我们要求的 \(c\) 满足的条件。

然后注意到位运算是可以按位拆开的,那么对于 \(c\) 也是如此,就有 \(c(i,j)=\prod_{k=0}^nc(i_k,j_k)\)

然后考虑知道了 \(c\) 之后如何转化成点值形式,也就是计算下面这个式子:

\[A'[i]=\sum_{j=0}^{2^n-1}c(i,j)A_j \]

把这个折半,就有:

\[A'[i]=\sum_{j=0}^{2^{n-1}-1}c(i,j)A_j+\sum_{j=2^{n-1}}^{2^n-1}c(i,j)A_j \]

把它的首位单独拿出来,写 \(c(i,j)=c(i_0,j_0)c(i',j')\) ,就有:

\[A'[i]=c(i_0,0)\sum_{j=0}^{2^{n-1}-1}c(i',j')A_j+c(i_0,1)\sum_{j=2^{n-1}}^{2^n-1}c(i',j')A_j \]

这样子问题就减半了,可以分治下去。

最后我们需要求 \(c\) ,这个就变得好求很多了,因为 \(c(i,j)\) 中的 \(i,j\in\{0,1\}\) ,然后逆变换对矩阵求逆就可以了。

那么我们写出这个矩阵

\[\begin{bmatrix}c(0,0) & c(0,1)\\c(1,0) & c(1,1)\end{bmatrix} \]

\(or\) 运算为例,我们可以得到关系式:

\[c(0,0)=c(0,0)c(0,0) \rightarrow c(i,j)\in\{0,1\} \]

\[c(0,1)=c(0,0)c(0,1),c(1,1)=c(1,0)c(1,1) \]

通过上面的式子可以计算出好几种矩阵,但还需要满足矩阵有逆,那么得到其中一种矩阵:

\[c=\begin{bmatrix}1 & 0\\1 & 1\end{bmatrix},c^{-1}=\begin{bmatrix}1 & 0\\-1 & 1\end{bmatrix} \]

同理,对于 \(and\) ,就有:

\[c=\begin{bmatrix}0 & 1\\1 & 1\end{bmatrix},c^{-1}=\begin{bmatrix}-1 & 1\\1 & 0\end{bmatrix} \]

对于 \(xor\) ,就有:

\[c=\begin{bmatrix}1 & -1\\1 & 1\end{bmatrix},c^{-1}=\begin{bmatrix}\frac{1}{2} & \frac{1}{2}\\-\frac{1}{2} & \frac{1}{2}\end{bmatrix} \]

这样子,我们也就可以在 \(O(n\log n)\) 的时间内解决这个问题了。

例题

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

就是引入的那道题。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 2e5;
const int p = 998244353;
using namespace std;
int n,a[N + 5],b[N + 5],c[3][3],A[N + 5],B[N + 5];
int mypow(int a,int x){int s = 1;for (;x;x & 1 ? s = 1ll * a * s % p : 0,a = 1ll * a * a % p,x >>= 1);return s;}
void solve(int *a,int n)
{
    for (int i = 1;i < (1 << n);i <<= 1)
        for (int j = 0;j < (1 << n);j += i << 1)
            for (int k = 0;k < i;k++)
            {
                int a0 = a[j + k],a1 = a[j + k + i];
                a[j + k] = (1ll * c[1][1] * a0 % p + 1ll * c[1][2] * a1 % p) % p;
                a[j + k + i] = (1ll * c[2][1] * a0 % p + 1ll * c[2][2] * a1 % p) % p;
            }
}
void solveor()
{
    c[1][2] = 0;
    c[1][1] = c[2][1] = c[2][2] = 1;
    for (int i = 0;i < (1 << n);i++)
        A[i] = a[i],B[i] = b[i];
    solve(A,n);
    solve(B,n);
    for (int i = 0;i < (1 << n);i++)
        A[i] = 1ll * A[i] * B[i] % p;
    c[1][1] = c[2][2] = 1;
    c[2][1] = -1;c[1][2] = 0;
    solve(A,n);
    for (int i = 0;i < (1 << n);i++)
        printf("%d ",(A[i] + p) % p);
    cout<<endl;
}
void solveand()
{
    c[1][1] = 0;
    c[1][2] = c[2][1] = c[2][2] = 1;
    for (int i = 0;i < (1 << n);i++)
        A[i] = a[i],B[i] = b[i];
    solve(A,n);
    solve(B,n);
    for (int i = 0;i < (1 << n);i++)
        A[i] = 1ll * A[i] * B[i] % p;
    c[1][1] = -1;c[2][2] = 0;
    c[2][1] = c[1][2] = 1;    
    solve(A,n);
    for (int i = 0;i < (1 << n);i++)
        printf("%d ",(A[i] + p) % p);
    cout<<endl;
}
void solvexor()
{
    c[1][2] = -1;
    c[2][1] = c[1][1] = c[2][2] = 1;
    for (int i = 0;i < (1 << n);i++)
        A[i] = a[i],B[i] = b[i];
    solve(A,n);
    solve(B,n);
    for (int i = 0;i < (1 << n);i++)
        A[i] = 1ll * A[i] * B[i] % p;
    c[1][1] = c[1][2] = c[2][2] = mypow(2,p - 2);
    c[2][1] = -mypow(2,p - 2);
    solve(A,n);
    for (int i = 0;i < (1 << n);i++)
        printf("%d ",(A[i] + p) % p);
    cout<<endl;
}
int main()
{
    scanf("%d",&n);
    for (int i = 0;i < (1 << n);i++)
        scanf("%d",&a[i]);
    for (int i = 0;i < (1 << n);i++)
        scanf("%d",&b[i]);
    solveor();
    solveand();
    solvexor();
    return 0;
}
  1. 洛谷 P3175 [HAOI2015]按位或
  1. CF449D Jzzhu and Numbers
  1. CF1119H Triple
posted @ 2021-01-18 14:21  eee_hoho  阅读(117)  评论(0编辑  收藏  举报