FWT 笔记 II

为了实现 FWT,我们需要实现所谓带权前缀和。

具体来说,给定序列 \(\{A\}_{i=0}^{c^n-1}\)(这里我们推广了一般的二进制情况),以及一个函数 \(w(i,j)\quad i \in [0,d^n),j\in [0,c^n)\),我们需要求出:

\[\textup{FWT}(A)_i=\sum_{j}w(i,j)A_j \]

我们记 \(x^{(y)}_z\) 表示 \(x\)\(y\) 进制下的第 \(z+1\) 位。

当上下文语意明确时,我们省略上标。

一般为了方便求解,\(w\) 有性质:

\[w(x,y)=\prod_{0\le i<n}w(x^{(d)}_i,y^{(c)}_i) \]

好的,怎么求解这个一般的 FWT 呢?

我们这里可以建出 \(c\) 进制的字典树,每一次我们维护子树内变换后的结果。

那么用转移的直接合并就可以了。

对于 \(c=d\) 的情况,可以原地非递归实现。

复杂度若 \(c=d\)\(\Theta(nc^{n+1})\) 的,否则是 \(\Theta(c\sum_{0\le i<n}c^id^{n-i})=\Theta\left(cd^n\dfrac{1-(c/d)^n}{1-c/d}\right)\)

我们一般认为 \(c,d\) 是常数,于是复杂度 \(\Theta(\max\{c,d\}^n)\)(如果 \(c\ne d\)。)

接下来考虑做卷积。

卷积

卷积就是给定两个序列 \(\{A_i\}_{i=0}^{c^n-1},\{B_i\}^{c^n-1}_{i=0}\),以及 \(c\) 进制位运算 \(\textup{op}\),求:

\[C_k=\sum_{i\textup{ op }j=k}A_iB_j \]

我们一般需要三个 \(\textup{FWT}\),使得 \(C=\textup{FWT}_3(\textup{FWT}_1(A)\cdot \textup{FWT}_2(B))\),这里 \(\cdot\) 表示对应相乘。

我们记三个 \(\textup{FWT}\)\(w\) 分别为 \(w_1,w_2,w_3\),再记 \(C_k=\sum_{i,j}w(k,i,j)A_iB_j\)

那就是

\[C_k=\sum_{\mathscr{l}}w_3(k,\mathscr{l})\sum_{i,j}w_1(\mathscr{l},i)w_2(\mathscr{l},j)A_iB_j=\sum_{i,j}w(k,i,j)A_iB_j \]

也就是说

\[w(k,i,j)=\sum_{\mathscr{l}}w_3(k,\mathscr{l})w_1(\mathscr{l},i)w_2(\mathscr{l},j) \]

我们知道这些 \(w\) 每一位都是独立的,所以:

\[\begin{aligned} \prod_{p}w(k_p,i_p,j_p)=&\sum_{\mathscr{l}}\prod_{p}w_3(k_p,\mathscr{l}_p)w_1(\mathscr{l}_p,i_p)w_2(\mathscr{l}_p,j_p)\\ =&\prod_{p}\sum_{0\le \mathscr{l}_p<d}w_3(k_p,\mathscr{l}_p)w_1(\mathscr{l}_p,i_p)w_2(\mathscr{l}_p,j_p) \end{aligned} \]

第二步变换是因为 \(\mathscr{l}\) 恰好遍历了所有可能。

同时注意 \(\mathscr{l}\) 其实不一定是 \(c\) 进制的,这里我们设其为 \(d\) 进制。

于是

\[w(k_p,i_p,j_p)=\sum_{0\le \mathscr{l}<d}w_3(k_p,\mathscr{l})w_1(\mathscr{l},i_p)w_2(\mathscr{l},j_p) \]

这是什么?

我们记 \(\mathbf{W}^{(k)}_{i,j}=w(k,i,j),\lambda_{k,\mathscr{l}}=w_3(k,\mathscr{l}),\mathbf{V}^{(\mathscr{l})}_{i,j}=w_1(\mathscr{l},i)w_2(\mathscr{l},j)\quad 0\le i,j,k<c\quad 0\le \mathscr{l}<d\)

那么就可以简单地写成:

\[\mathbf{W}^{(k)}=\sum_{\mathscr{l}}\lambda_{k,\mathscr{l}}\mathbf{V}^{(\mathscr{l})} \]

也就是说,给定 \(c\)\(c\times c\) 矩阵。我们需要构造 \(d\)\(c\times c\) 特殊的矩阵,使得给定的每个矩阵都可以表示为构造矩阵的线性组合。

你构造的矩阵应该可以被写成列向量和行向量的乘积,也就是说这些矩阵每行每列成比例(秩为 \(1\)),当然构造的矩阵越少越好。

这被称作张量分解。

只要知道张量分解,我们便能知道这三个 \(\textup{FWT}\) 变换分别是什么从而实现。

假设卷积是 \(c\) 进制的,你使用了 \(d\) 个矩阵,若 \(c\ne d\),复杂度为 \(\mathcal{O}(\max\{c,d\}^n)\),若 \(c=d\),复杂度为 \(\mathcal{O}(nc^n)\)

但是,最小张量分解是 NP-Hard 的,这使得 \(\textup{FWT}\) 很大程度上依赖于直觉(或者是超级暴力。)

例题

ARC132F Takahashi The Strongest

首先可以转换为从未获胜的概率。

考虑一下,这其实就是卷积,我们用矩阵写成这样:

\[\begin{pmatrix}1&1&1\\1&0&1\\1&1&1\end{pmatrix}\quad \begin{pmatrix}1&1&1\\1&1&1\\1&1&0\end{pmatrix}\quad \begin{pmatrix}0&1&1\\1&1&1\\1&1&1\end{pmatrix} \]

怎么构造张量分解?

观察数据范围,我们可以用 \(4\) 个矩阵。

那分别是

\[\begin{pmatrix}1&1&1\\1&1&1\\1&1&1\end{pmatrix}\quad \begin{pmatrix}0&0&0\\0&1&0\\0&0&0\end{pmatrix}\quad \begin{pmatrix}0&0&0\\0&0&0\\0&0&1\end{pmatrix}\quad \begin{pmatrix}1&0&0\\0&0&0\\0&0&0\end{pmatrix} \]

直接 FWT。

#include<bits/stdc++.h>
using namespace std;
using ull=unsigned long long;
int n;
int pwc[13],pwd[13];
void init(){
    pwc[0]=pwd[0]=1;
    for(int i=1;i<=n;++i){
        pwc[i]=pwc[i-1]*3;
        pwd[i]=pwd[i-1]*4;
    }
}
int w1[4][3]={
    {1,1,1},
    {0,1,0},
    {0,0,1},
    {1,0,0}
};
int w2[3][4]={
    {1,-1,0,0},
    {1,0,-1,0},
    {1,0,0,-1}
};
const int V=2e7;
ull tmp[V+5];
void FWT(int now,ull* A,ull *B){
    if(now==n){
        B[0]=A[0];
        return;
    }
    for(int i=0;i<3;++i) FWT(now+1,A+i*pwc[n-now-1],B+i*pwd[n-now-1]);
    for(int i=0;i<pwd[n-now];++i) tmp[i]=0;
    for(int i=0;i<4;++i){
        for(int j=0;j<3;++j){
            for(int k=0;k<pwd[n-now-1];++k){
                tmp[i*pwd[n-now-1]+k]+=w1[i][j]*B[j*pwd[n-now-1]+k];
            }
        }
    }
    for(int i=0;i<pwd[n-now];++i) B[i]=tmp[i];
}
void IFWT(int now,ull *A,ull *B){
    if(now==n){
        B[0]=A[0];
        return;
    }
    for(int i=0;i<4;++i) IFWT(now+1,A+i*pwd[n-now-1],B+i*pwd[n-now-1]);
    for(int i=0;i<pwc[n-now];++i) tmp[i]=0;
    for(int i=0;i<3;++i){
        for(int j=0;j<4;++j){
            for(int k=0;k<pwc[n-now-1];++k){
                tmp[i*pwc[n-now-1]+k]+=w2[i][j]*B[j*pwd[n-now-1]+k];
            }
        }
    }
    for(int i=0;i<pwc[n-now];++i) B[i]=tmp[i];
}
ull AA[V+5],BB[V+5],CC[V+5],DD[V+5];
int m,k;
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;++i){
        string str;
        cin>>str;
        int res=0;
        for(int j=0;j<n;++j){
            res=res*3+(str[j]=='P'?0:(str[j]=='R'?1:2));
        }
        ++AA[res];
    }
    for(int i=1;i<=k;++i){
        string str;
        cin>>str;
        int res=0;
        for(int j=0;j<n;++j){
            res=res*3+(str[j]=='P'?0:(str[j]=='R'?1:2));
        }
        ++BB[res];
    }
    init();
    FWT(0,AA,CC);
    FWT(0,BB,DD);
    for(int i=0;i<pwd[n];++i){
        CC[i]*=DD[i];
    }
    IFWT(0,CC,DD);
    for(int i=0;i<pwc[n];++i){
        cout<<(ull)m*k-DD[i]<<"\n";
    }
    return 0;
}
posted @ 2026-04-26 00:07  ThisIsLu  阅读(30)  评论(0)    收藏  举报