快速沃尔什变换(FWT)

本文的开始,通过一道模板题来认识快速沃尔什变换。

题面

P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT / FWT)
给定 \(A,B\),求 \(\sum_{i|j=k}A_i\times B_j,\sum_{i\&j=k}A_i\times B_j,\sum_{i\oplus j=k}A_i\times B_j\)。

解法

证明过于复杂,此处不再赘述,若有兴趣,可见 https://www.luogu.com.cn/article/2pavj2pd 。
那么,根据上面那篇文章,我们可以得到以下几个函数:

void OR(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
                f[i+j+k]+=f[i+j]*x;
}
void AND(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
                f[i+j]+=f[i+j+k]*x;
}
void XOR(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
            {
                modint u=f[i+j],v=f[i+j+k];
                f[i+j]=(u+v)*x;
                f[i+j+k]=(u-v)*x;
            }
}

那么,我们完成了 FWT,但是你会发现输出不对劲,为什么呢?因为数列变换过了。所以,我们就需要给他逆变换回去。还是上面那篇文章,证明了 \(or,and\) 的逆变换只需取 \(x=-1\),\(xor\) 的逆变换则是 \(x=\cfrac{1}{2}\)。
综上所述,我们便得到了如下代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=(1<<17)+1,mod=998244353;
int n,m;
struct modint//取模类
{
    int x;
    modint(int c=0):x((c%mod+mod)%mod){}
    void in()
    {
        scanf("%d",&x);
    }
    int ksm(int b)const
    {
        int res=1,a=x;
        while(b)
        {
            if(b&1) res=1ll*res*a%mod;
            a=1ll*a*a%mod;b>>=1;
        }
        return res;
    }
    int nv()const
    {
        return ksm(mod-2);
    }
    void pu()const
    {
        printf("%d ",x);
    }
    modint& operator +=(const modint&b){x=(x+b.x)%mod;return *this;}
    modint& operator -=(const modint&b){x=((x-b.x)%mod+mod)%mod;return *this;}
    modint& operator *=(const modint&b){x=1ll*x*b.x%mod;return *this;}
    modint& operator /=(const modint&b){x=1ll*x*b.nv()%mod;return *this;}
    modint operator +(const modint&b) const{return modint((x+b.x)%mod);}
    modint operator -(const modint&b) const{return modint(((x-b.x)%mod+mod)%mod);}
    modint operator *(const modint&b) const{return modint(1ll*x*b.x%mod);}
    modint operator /(const modint&b) const{return modint(1ll*x*b.nv()%mod);}
};
modint a[MAXN],b[MAXN],A[MAXN],B[MAXN];
void init()
{
    for(int i=0;i<n;i++) a[i]=A[i],b[i]=B[i];
}
void get()
{
    for(int i=0;i<n;i++) a[i]*=b[i];
}
void put()
{
    for(int i=0;i<n;i++) a[i].pu();
    cout<<'\n';
}
void OR(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
                f[i+j+k]+=f[i+j]*x;
}
void AND(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
                f[i+j]+=f[i+j+k]*x;
}
void XOR(modint *f,modint x=1)
{
    for(int o=2,k=1;o<=n;o<<=1,k<<=1)
        for(int i=0;i<n;i+=o)
            for(int j=0;j<k;j++)
            {
                modint u=f[i+j],v=f[i+j+k];
                f[i+j]=(u+v)*x;
                f[i+j+k]=(u-v)*x;
            }
}
int main()
{
    cin>>m;n=1<<m;
    for(int i=0;i<n;i++) A[i].in();
    for(int i=0;i<n;i++) B[i].in();
    init();OR(a);OR(b);get();OR(a,mod-1);put();
    init();AND(a);AND(b);get();AND(a,mod-1);put();
    init();XOR(a);XOR(b);get();XOR(a,499122177);put();
}

那么,FWT 就这么讲完了吗?
可是,FWT 分明听着很高大上啊。

例题一

AT_arc132_f [ARC132F] Takahashi The Strongest
给定 \(n\) 个 A 能出的石头剪刀布策略,\(m\) 个 B 能出的策略,求每个 C 的策略中,能同时赢至少一次的方案数。

解法

这道题的分支乘法做法可见例题篇,这里讲一种不一样的 FWT 做法。
考虑将状态压成四进制数,然后定义新运算 \(P(x,y)=x*[x=y]\),若 \(a_i,b_i\) 表示 A 和 B 每种策略的数量,则我们要求:

\[c_k=\sum_{P(i,j)=k}{a_ib_j} \]

然后一眼就很像 FWT 的那个式子,于是用 FWT 算一下即可。
然后我们还想得到 \(d_i=\sum_{P(i,j)\neq 0}{c_j}\)。然而这东西一眼上去就不太可做的样子,考虑正难则反。设 \(dp_{i,j}\) 表示考虑了前 \(i\) 位,后面的位和 \(\sum_{P(j,i)=0}{c_j}\) 后面的位相同的方案数。转移枚举当前位填什么即可。
代码会补上的~~~

posted @ 2026-05-18 21:05  duchuyuanX  阅读(14)  评论(0)    收藏  举报