快速沃尔什变换(FWT)学习笔记
概述
FWT的大体思路就是把要求的 C(x)=A(x)×B(x) 即 \( c[i]=\sum\limits_{j?k=i} (a[j]*b[k]) \) 变换成这样的:\( c^{'}[i]=a^{'}[i]*b^{'}[i] \)。
只要知道 c'[ i ] 和 c[ i ] 的关系,就能把 A(x)、B(x) 变成 A'(x)、B'(x) ,从而算出 C'(x) ,再把 C'(x) 变成 C(x)。
或卷积
定义\( c^{'}[i]=\sum\limits_{j | i=i} c[j] \),则\( c^{'}[i]=a^{'}[i]*b^{'}[i] \)
证明:\( c^{'}[i]=\sum\limits_{j | i=i} c[j] \)
因为\( c[i]=\sum\limits_{j|k=i}a[j]*b[k] \)
所以 \( c^{'}[i]=\sum\limits_{(j|k)|i=i}a[j]*b[k] \)
\( =\sum\limits_{(j|k)|i=i}a[j]*b[k]=\sum\limits_{j|i=i}a[j] * \sum\limits_{k|i=i}b[k] \)
又有:\( a^{'}[i]=\sum\limits_{j|i=i}a[j] \) \( b^{'}[i]=\sum\limits_{j|i=i}b[j] \)
所以\( c^{'}[i]=a^{'}[i]*b^{'}[i] \)
接下来考虑怎么把 A(x) 变成 A'(x) 。
考虑按位来做,比如从低位到高位枚举,则每一部分左边一半的该位全是0、右边一半的该位全是1;记左边为A0,右边为A1。
如:00001111
00110011
01010101
如果已经算好了A0和A1,考虑用它们求出A。比如算好了第 1~2 位置的值A0和第 3~4 位置的值A1,想求第 1~4 位置的值A;(那么现在是枚举到了二进制第二位了)
此时的A0里没有A1位置对它的贡献,A1里也没有A0位置对它的贡献;考虑两部分位置的值怎样互相贡献;
考虑左边和右边的对应位置,它们只有最高位一个是0一个是1的不同;则是左边对应位置的子集的位置一定也是右边对应位置的子集,可以这样做:A0' = A0,A1' = A0+A1
所以模仿FFT的框架写一个就行了。(感觉这里求 \( \sum\limits_{j|i=i}a[j] \) 的思路和高维前缀和很像)
但不用弄那个 r[ ] 来换位置(因为不是弄偶数项和奇数项,而是真的前半部分和后半部分);不过就算换了位置也可以!
因为那样换一下位置相当于是每个位置的角标被翻转了,比如上面那8个位置的角标会变成:
01010101
00110011
00001111
这样的话,自己“从低位到高位枚举”可以看作从高位到低位枚举,一切就没问题了。主要是因为位运算每一位是独立的嘛。
它的逆变换是这样想:因为 A0' = A0,A1' = A0+A1;所以 A0 = A0',A1 = A1'-A0 = A1'-A0'。刚才是从低位到高位枚举的话,现在要从高位到低位枚举。
但其实还是从低位到高位枚举也是对的!
考虑一个位置k,它加上的那些 “对应位置” j 的特点是 j 只和 k 有一位不同。比如从低到高枚举到第3位的时候 k 位置的值加上了 j 位置的值,说明二进制第3位上 j 是0、k是1,第3位之前 j 和 k 一样(因为“对应”嘛),而第3位之后 j 和 k 其实也一样(因为第3位之后 j 和 k 就变成“一块"里的了,再高的位会一起变成0或1之类的);
从 A'(x) 变回 A(x) 的过程中,比如第一步的时候,每个 a[ i ] 都记录着所有 角标是 i 子集的a的权值和 ;
从低到高枚举到第一个 k 是1的位置(除了最低位),比如是第3位,则此时 a'[ k ] - a'[ j ] 减去的值是 “角标第3位是0、其余部分是 k 的子集” 的那些位置的值;剩下的就是 “角标第3位是1、其余部分是 k 的子集” 的值。
接下来枚举到下一个 k 是1的位置,比如是第5位;因为 j 的其它位上的值都和 k 一样,所以此时 j 也是经历过第3位时的一番操作;则此时 a'[ k ] - a'[ j ] 减去的值是“角标第3位是1、第5位是0、其余部分是 k 的子集”的那些位置的值;则 a'[ k ] 剩下的值是 “角标第3位是1、第5位是1、其余部分是 k 的子集” 的位置的值;
这样一直枚举到最后,剩下的就是 “角标在 k 是1的位上是1、其余位上是 k 的自己” 位置的值,即只剩正好的 a[ k ] 了,于是此时 a'[ k ] = a[ k ] 。
与卷积
和或卷积一样。变换:A0'=A0+A1,A1'=A1 逆变换:A0 = A0'-A1 = A0'-A1',A1=A1'
异或卷积
定义 \( c^{'}[i]=\sum\limits_{j \& i有偶数个1} c[j] - \sum\limits_{j \& i有奇数个1} c[j] \)
考虑证明 \( c^{'}[i]=a^{'}[i]*b^{'}[i] \)
证明:因为 \( c[i]=\sum\limits_{j \otimes k=i} a[j]*b[k] \)
所以 \( c^{'}[i]=\sum\limits_{ (j \otimes k)与 i 有偶数个1重合 } a[j]*b[k] - \sum\limits_{ (j \otimes k)与 i 有奇数个1重合 } a[j]*b[k] \)
又 \( a^{'}[i]*b^{'}[i] = ( \sum\limits_{j \& i有偶数个1}a[j] - \sum\limits_{j \& i有奇数个1}a[j] ) * ( \sum\limits_{j \& i有偶数个1}b[j] - \sum\limits_{j \& i有奇数个1}b[j] ) \)
\( = \sum\limits_{j \& i有偶数个1}a[j]*b[j] + \sum\limits_{j \& i有奇数个1}a[j]*b[j] - \sum\limits_{j \& i有偶数个1,k \& i有奇数个1}a[j]*b[k] - \sum\limits_{j \& i有奇数个1,k \& i有偶数个1}a[j]*b[k] \)
\( = \sum\limits_{j \& i与k \& i的1的个数奇偶性相同}a[j]*b[k] - \sum\limits_{j \& i与k \& i的1的个数奇偶性不同}a[j]*b[k] \)
\( = \sum\limits_{(j \otimes k)与 i 有偶数个1重合}a[j]*b[k] - \sum\limits_{(j \otimes k)与 i 有奇数个1重合}a[j]*b[k] \)
(这一步等价是因为异或的时候,如果 j 和 k 有公共位置的1,那么一次会消掉2个1;所以 ( (j&i)的1的个数 + (k&i)的1的个数 ) 在 j 和 k 异或之后奇偶性不会变)
所以 \( c^{'}[i]=a^{'}[i]*b^{'}[i] \)
接下来考虑怎么把A(x)变成A'(x)。
还是有前一半的A0和后一半的A1。对应位置 & 起来之后,那个最高位还是0;
所以对于A0里的一个a[ i ]来说,记和它 & 起来的那些位置 j (其实 j 遍历了所有A0里的位置)在A1里的对应位置为 j' ,则 j & i == j' & i;所以A0'=A0+A1;
而对于A1里的一个a[ i ]来说,算A1的时候A1的标号的最高位还没被考虑(即视作0),合并的时候A1的最高位变成1了;设 i 在A1的 & 起来的那些位置 j 在A0里的对应位置为 j',则 j & i 比 j' & i 多了一个1(最高位即当前枚举到的位),所以当 i 和A1里的 j 匹配时,单独算A1时算好的 a[ i ] = sigma - sigma 里的两个 sigma 的位置换了一下,也就是符号变了;所以A1'=A0-A1。
它的逆变换就是:A0=(A0'+A1')/2,A1=(A0'-A1')/2。
关于实现方法的讨论就和或卷积一样。
模板
洛谷4717 【模板】快速沃尔什变换
题目:https://www.luogu.org/problemnew/show/P4717
不知为何跑得很慢。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=(<<)+,mod=;
int n,a[][N],b[][N],c[][N],len,r[N],inv;
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='') ret=(ret<<)+(ret<<)+ch-'',ch=getchar();
return fx?ret:-ret;
}
int g[];
void wrt(int x)
{
if(x<)putchar('-'),x=-x;
if(!x){printf("0 ");return;}
int t=;while(x)g[++t]=x%,x/=;
while(t)putchar(g[t]+''),t--;putchar(' ');
}
void upd(int &x){x>=mod?x-=mod:;}
void fwt0(int *a,bool fx)
for(int R=;R<=len;R<<=)
for(int i=,m=R>>;i<len;i+=R)
for(int j=;j<m;j++)
(fx?a[i+m+j]+=mod-a[i+j]:a[i+m+j]+=a[i+j]),upd(a[i+m+j]);
}
void fwt1(int *a,bool fx)
for(int R=;R<=len;R<<=)
for(int i=,m=R>>;i<len;i+=R)
for(int j=;j<m;j++)
(fx?a[i+j]+=mod-a[i+m+j]:a[i+j]+=a[i+m+j]),upd(a[i+j]);
}
void fwt2(int *a,bool fx)
for(int R=;R<=len;R<<=)
{
for(int i=,m=R>>;i<len;i+=R)
for(int j=;j<m;j++)
{
int x=a[i+j]+a[i+m+j],y=a[i+j]+mod-a[i+m+j];
upd(x); upd(y);
fx?(x=(ll)x*inv%mod,y=(ll)y*inv%mod):;
a[i+j]=x; a[i+m+j]=y;
}
}
}
int main()
{
n=(<<rdn());
for(int i=;i<n;i++)a[][i]=a[][i]=a[][i]=rdn();
for(int i=;i<n;i++)b[][i]=b[][i]=b[][i]=rdn();
len=n<<;
for(int i=;i<len;i++)r[i]=(r[i>>]>>)+((i&)?len>>:);
int k=mod-,tmp=;inv=;
while(k){if(k&)inv=(ll)inv*tmp%mod;tmp=(ll)tmp*tmp%mod;k>>=;}
fwt0(a[],); fwt0(b[],); fwt1(a[],); fwt1(b[],); fwt2(a[],); fwt2(b[],);
for(int t=;t<;t++)
for(int i=;i<len;i++)
c[t][i]=(ll)a[t][i]*b[t][i]%mod;
fwt0(c[],); fwt1(c[],); fwt2(c[],);
for(int t=;t<;t++,puts(""))
for(int i=;i<n;i++)wrt(c[t][i]);
return ;
}
快速沃尔什变换(FWT)学习笔记的更多相关文章
- 一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记
一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记 曾经某个下午我以为我会了FWT,结果现在一丁点也想不起来了--看来"学"完新东西不经常做题不写博客,就白学了 = = 我没啥智 ...
- 快速沃尔什变换 FWT 学习笔记【多项式】
〇.前言 之前看到异或就担心是 FWT,然后才开始想别的. 这次学了 FWT 以后,以后判断应该就很快了吧? 参考资料 FWT 详解 知识点 by neither_nor 集训队论文 2015 集合幂 ...
- 快速沃尔什变换 (FWT)学习笔记
证明均来自xht37 的洛谷博客 作用 在 \(OI\) 中,\(FWT\) 是用于解决对下标进行位运算卷积问题的方法. \(c_{i}=\sum_{i=j \oplus k} a_{j} b_{k} ...
- 快速沃尔什变换(FWT)学习笔记 + 洛谷P4717 [模板]
FWT求解的是一类问题:\( a[i] = \sum\limits_{j\bigoplus k=i}^{} b[j]*c[k] \) 其中,\( \bigoplus \) 可以是 or,and,xor ...
- 【学习笔鸡】快速沃尔什变换FWT
[学习笔鸡]快速沃尔什变换FWT OR的FWT 快速解决: \[ C[i]=\sum_{j|k=i} A[j]B[k] \] FWT使得我们 \[ FWT(C)=FWT(A)*FWT(B) \] 其中 ...
- FMT/FWT学习笔记
目录 FMT/FWT学习笔记 FMT 快速莫比乌斯变换 OR卷积 AND卷积 快速沃尔什变换(FWT/XOR卷积) FMT/FWT学习笔记 FMT/FWT是算法竞赛中求or/and/xor卷积的算法, ...
- FWT学习笔记
FWT学习笔记 引入 一般的多项式乘法是这样子的: \(c_i=\sum_{i,j}a_j*b_k*[j+k==i]\) 但是如果我们将这个乘法式子里面的+号变换一下变成其他的运算符号呢? \(c_i ...
- 快速沃尔什变换FWT
快速沃尔什变换\(FWT\) 是一种可以快速完成集合卷积的算法. 什么是集合卷积啊? 集合卷积就是在集合运算下的卷积.比如一般而言我们算的卷积都是\(C_i=\sum_{j+k=i}A_j*B_k\) ...
- FWT 学习笔记
FWT学习笔记 好久以前写的,先粘上来 定义数组 \(n=2^k\) \(A=[a_0,a_1,a_2,a_3,...,a_{n-1}]\) 令\(A_0=[a_0,a_1,a_2,...,a_{\f ...
- 集合并卷积的三种求法(分治乘法,快速莫比乌斯变换(FMT),快速沃尔什变换(FWT))
也许更好的阅读体验 本文主要内容是对武汉市第二中学吕凯风同学的论文<集合幂级数的性质与应用及其快速算法>的理解 定义 集合幂级数 为了更方便的研究集合的卷积,引入集合幂级数的概念 集合幂级 ...
随机推荐
- Python编程-多态、封装、特性
一.多态与多态性 1.多态 (1)什么是多态 多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承) 序列类型有多种形态:字符串,列表,元组. 动物有多种形态:人,狗,猪 文 ...
- Python编程-常用模块及方法
常用模块介绍 一.time模块 在Python中,通常有这几种方式来表示时间: 时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们运行 ...
- 跨平台移动开发 Xuijs超轻量级的框架 Dom与Event简洁代码实现文本展开收起
Dom与Event简洁代码实现文本展开收起 Xuijs超轻量级的框架 Dom与Event实现文本展开收起 效果图 示例代码 <!DOCTYPE html PUBLIC "-//W3C/ ...
- python爬虫之urllib库
请求库 urllib urllib主要分为几个部分 urllib.request 发送请求urllib.error 处理请求过程中出现的异常urllib.parse 处理urlurllib.robot ...
- INSPIRED启示录 读书笔记 - 第9章 产品副经理
发现帮手 从本质上讲,产品就是创意,产品经理的职责是想出好点并加以实现.我们需要好点子,有些想法是我们自己的创意,但如果仅依靠自己,就会严重限制创意的发挥 做产品要找公司最聪明的人合作,发现公司里潜在 ...
- 手把手教你使用eclipse+qemu+gdb来单步调试ARM内核【学习笔记】
平台信息:linux4.0 平台:qemu 作者:庄泽彬 说明:笨叔叔的Linux视频的笔记 一.编译linux源码 export CROSS_COMPILE=arm-linux-gnueabi- e ...
- ceph安装各种报错
[ceph_deploy][ERROR ] RuntimeError: Failed to execute command: ceph-disk-activate –mark-init sysvini ...
- MongoDB快速入门(二)- 数据库
创建数据库 MongoDB use DATABASE_NAME 用于创建数据库.该命令如果数据库不存在,将创建一个新的数据库, 否则将返回现有的数据库. 语法 use DATABASE语句的基本语法如 ...
- Java编程思想 Random(47)
Random类包含两个构造方法,下面依次进行介绍:1. public Random()该构造方法使用一个和当前系统时间对应的相对时间有关的数字作为种子数,然后使用这个种子数构造Random对象.2. ...
- js建造者(生成器)模式
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 在软件系统中,有时需要创建一个复杂对象,并且这个复杂对象由其各部分子对象通过一定的步骤组合而成. 建造者模式类图: ...