二进制卷积(FWT,子集卷积)
来解决一下二进制卷积相关。以下均假设序列长度为 \(2\) 的次幂。
我觉得以后我行内 \(\LaTeX\) 应该加个空格。
快速沃尔什变换(Fast Walsh Transform,FWT)
这个是拿来在 \(O(n\log n)\) 时间复杂度内处理位运算(与、或、异或)卷积的。
重申一遍卷积:两个序列 \(f,g\) 的卷积是
当中间的二元运算是三种位运算中的一种的时候,就变成了位运算卷积。我们一个一个讨论。
- 或运算
我们定义对于序列 \(f\) 的变换 \(FWT(f)\) 的第 \(i\) 项为
也就是我们要求以 \(i\) 的所有子集为下标的元素和。
考虑类似FFT的分治做法,我们设 \(f_0\) 为所有二进制位开头为 \(0\) 的数(就是前一半), \(f_1\) 为二进制位开头为 \(1\) 的数(后一半),那么前一半的子集就是它自己的子集,而后一半的子集除了后一半自己的,还有前一半对应位置的(因为它自己的最高位是 \(1\) ,对应的最高位是 \(0\) 的仍然是它的子集)。所以我们有:
其中 \(\text{merge}\) 表示把前后两个序列拼起来,加法表示对应位相加(就是最高位带 \(1\) 的项加上最高位不带 \(1\) 的项)。显然这个可以像FFT一样迭代。
然后就可以普通地把每一项对应相乘了。略证为什么直接乘是对的:
然后关于逆变换,我们知道 \(f_0\) 的子集是它自己的, \(f_1\) 的子集是它自己的减去 \(f_0\) 的,那么我们类似上面的式子,有:
显然两个可以扔到一起。
void getor(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
a[i+j+mid]=(a[i+j+mid]+1ll*a[i+j]*tp)%mod;
}
}
}
}
- 与运算
类似或运算,我们定义序列 \(f\) 的变换 \(FWT(f)\) 的第 \(i\) 项为
然后定义 \(f_0,f_1\) 同上,那么 \(f_1\)的超集就是自己的超集, \(f_0\)的超集就是自己的加上 \(f_1\) 的,于是类似或,我们得到了:
void getand(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
a[i+j]=(a[i+j]+1ll*a[i+j+mid]*tp)%mod;
}
}
}
}
- 异或运算
我们构造运算 \(i\oplus j\) 为\(i\ \&\ j\)中 \(1\) 的数量的奇偶性,那么发现这个运算满足:
证明考虑大力分讨即可。(我不想码字)
关于二进制第\(p\)位,有如下三种情况:
- \(i\oplus k\) 为 \(1\) , \(j\oplus k\) 为 \(0\),则 \(i,k\) 为 \(1\) , \(j\) 为 \(0\) , \((i\ \text{xor}\ j)\oplus k\)为 \(1\)。反过来同理。
- \(i\oplus k\) 为 \(1\) , \(j\oplus k\) 为 \(1\),则 \(i,j,k\) 为 \(1\) , \((i\ \text{xor}\ j)\oplus k\)为 \(0\)。
- \(i\oplus k\) 为 \(0\) , \(j\oplus k\) 为 \(0\),则 \(i,j\) 为 \(0\) , \(k\) 为 \(0\) 或 \(1\) , \((i\ \text{xor}\ j)\oplus k\)为 \(0\)。
综上得证。那么定义序列 \(f\) 的变换 \(FWT(f)\) 的第 \(i\) 项为
那么乘起来有:
然后考虑如何运算。仍然考虑分治的思想,如上定义 \(f_0,f_1\) 。对于 \(f_0\) ,最高位满足 \(0\ \&\ 0=0,0\ \&\ 1=0\) ,所以前一半可以直接加上后一半的对应位置的值。而后一半有所不同,有\(1\ \&\ 0=0,1\ \&\ 1=1\),所以自己的贡献是负的。即:
逆变换同理,解方程即可。
void getxor(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
int x=a[i+j],y=a[i+j+mid];
a[i+j]=1ll*(x+y)*tp%mod;
a[i+j+mid]=1ll*(x-y+mod)*tp%mod;
}
}
}
}
那么我们就有了板子的全部代码。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int mod=998244353,inv2=(mod+1)>>1;
int a[1<<17],b[1<<17],n,p[1<<17],q[1<<17];
void getor(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
a[i+j+mid]=(a[i+j+mid]+1ll*a[i+j]*tp)%mod;
}
}
}
}
void getand(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
a[i+j]=(a[i+j]+1ll*a[i+j+mid]*tp)%mod;
}
}
}
}
void getxor(int a[],int n,int tp){
for(int mid=1;mid<n;mid<<=1){
for(int i=0;i<n;i+=(mid<<1)){
for(int j=0;j<mid;j++){
int x=a[i+j],y=a[i+j+mid];
a[i+j]=1ll*(x+y)*tp%mod;
a[i+j+mid]=1ll*(x-y+mod)*tp%mod;
}
}
}
}
void get(){for(int i=0;i<(1<<n);i++)p[i]=a[i],q[i]=b[i];}
void calc(){for(int i=0;i<(1<<n);i++)p[i]=1ll*p[i]*q[i]%mod;}
void print(){for(int i=0;i<(1<<n);i++)printf("%d ",p[i]);printf("\n");}
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]);
get();getor(p,1<<n,1);getor(q,1<<n,1);calc();getor(p,1<<n,mod-1);print();
get();getand(p,1<<n,1);getand(q,1<<n,1);calc();getand(p,1<<n,mod-1);print();
get();getxor(p,1<<n,1);getxor(q,1<<n,1);calc();getxor(p,1<<n,inv2);print();
}
子集卷积
子集卷积可以在 \(O(n\log^2n)\) 的复杂度内求出形如
这样的卷积形式。
首先或的要求我们直接 \(\text{or}\) 卷积就行。然后是与为 \(0\) 。我们可以多增加一维,记录每个数的 \(1\) 的个数,因为 \(|i\cap j|=0\Leftrightarrow |i|+|j|=|i\cup j|\) 。这样,我们把每个序列拆开,对每个位数做 \(FWT\) ,最后卷积起来再 \(FWT\) 回去就行了。具体的看代码。
int main(){
scanf("%d",&n);
for(int i=0;i<(1<<n);i++)scanf("%d",&a[__builtin_popcount(i)][i]);
for(int i=0;i<(1<<n);i++)scanf("%d",&b[__builtin_popcount(i)][i]);
//__builtin_popcount()是内置的统计数字二进制位中1的个数的函数 应该是O(1)的
for(int i=0;i<=n;i++){
getor(a[i],1<<n,1),getor(b[i],1<<n,1);
}
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<(1<<n);k++){
p[i][k]=(p[i][k]+1ll*a[j][k]*b[i-j][k]%mod)%mod;//对每一位变换之后按照定义乘
}
}
}
for(int i=0;i<=n;i++)getor(p[i],1<<n,mod-1);//逆变换回来
for(int i=0;i<(1<<n);i++)printf("%d ",p[__builtin_popcount(i)][i]);
}