二进制卷积(FWT,子集卷积)
来解决一下二进制卷积相关。以下均假设序列长度为
我觉得以后我行内
快速沃尔什变换(Fast Walsh Transform,FWT)
这个是拿来在
重申一遍卷积:两个序列
当中间的二元运算是三种位运算中的一种的时候,就变成了位运算卷积。我们一个一个讨论。
- 或运算
我们定义对于序列
也就是我们要求以
考虑类似FFT的分治做法,我们设
其中
然后就可以普通地把每一项对应相乘了。略证为什么直接乘是对的:
然后关于逆变换,我们知道
显然两个可以扔到一起。
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;
}
}
}
}
那么我们就有了板子的全部代码。
#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();
}
子集卷积
子集卷积可以在
这样的卷积形式。
首先或的要求我们直接
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]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)