FWT快速沃尔什变换学习笔记

FWT快速沃尔什变换学习笔记

1、FWT用来干啥啊

回忆一下多项式的卷积\(C_k=\sum_{i+j=k}A_i*B_j\)

我们可以用\(FFT\)来做。

甚至在一些特殊情况下,我们\(C_k=\sum_{i*j=k}A_i*B_j\)也能做(SDOI2015 序列统计)。

但是,如果我们把操作符换一下呢?

比如这样?

\(C_k=\sum_{i|j=k}A_i*B_j\)

\(C_k=\sum_{i\&j=k}A_i*B_j\)

\(C_k=\sum_{i\wedge j=k}A_i*B_j\)

似乎这就不能用\(FFT\)来做了。

这样子就有了\(FWT\)——用来解决多项式的位运算卷积

2、FWT大概要怎么搞啊

我们想一想\(FFT\)在干啥?

先对于一个多项式求出他在若干个单位根的点值表示法

再将多项式乘起来,最后再复原。

那么,我们可不可以用一个类似的思路呢?

先将多项式求出另外一个多项式\(FWT(A)\),再将对应的位置乘起来,最后再复原?

也就是\(FWT(C)=FWT(A)*FWT(B)\)(这个不是卷积,是对应位置相乘)?

废话,显然可以,要不然我还写什么\(FWT\)呢?

我们先来一点奇奇怪怪的记号吧。

因为多项式可以看成一个\(n\)维向量

所以,我们定义以下东西:

\(A+B=(A_0+B_0,A_1+B_1,......)\)

\(A-B=(A_0-B_0,A_1-B_1,......)\)

\(A\oplus B=(\sum_{i\oplus j=0}A_i*B_j,\sum_{i\oplus j=1}A_i*B_j,......)\)

3、或(or)卷积

或卷积长成这个样子:\(C_k=\sum_{i|j=k}A_i*B_j\)

写成向量的形式也就是这样子:\(A|B=(\sum_{i|j=0}A_i*B_j,\sum_{i|j=1}A_i*B_j,......)\)

很显然的一点:这个东西满足交换律,也就是\(A|B=B|A\)

再来仔细的看看,这个东西也满足结合律。

简单的证明一下\((A+B)|C=(\sum_{i|j=0}(A_i+B_i)*C_j,......)\)

很明显可以把括号拆开,然后分成两个\(\sum\),也就是\(A|C+B|C\)

我们这样子定义一下:

对于一个多项式\(A\)(最高次项是\(2^n\)),

我们把它分成两部分\(A_0,A_1\),分别表示前\(2^{n-1}\)次项和后面的\(2^{n-1}\)次项

也就是最高位为\(0\)与\(1\)的两部分。

对于或卷积,我们有:

\[FWT(A)=\begin{cases}(FWT(A_0),FWT(A_0+A_1)) & n\gt0 \\ A & n=0\end{cases}\]

对于\(n=0\)的时候,这个是非常显然的(常数还不显然了。。。)

啥?你问打个括号,然后中间一个逗号是啥意思?

不是说了这个结果是一个\(2^n\)维向量?

也就表示前\(2^{n-1}\)项是逗号前面的东西,后面那几项是逗号后面的东西

完全可以理解为将两个多项式强行前后拼接成一个新的多项式。

好的,我们来伪证(感性理解)一下\(n>0\)的时候的式子

对于\(A_0\)中的任意一项,如果在做\(or\)卷积的时候,和任意一个\(A_1\)中的项\(or\)了一下

那么它的首位一定是\(1\),必定不会对于\(FWT(A_0)\)产生任何贡献,

所以\(FWT(A)\)的前\(2^{n-1}\)项一定等于\(FWT(A_0)\)。

后面这个东西看起来就很不好证明了,所以我们先考虑证明点别的东西。

\(FWT(A+B)=FWT(A)+FWT(B)\)

证明(伪):

对于一个多项式\(A\)的\(FWT(A)\),它一定只是若干个原多项式中的若干项的若干倍的和。

即\(FWT(A)\)一定不包含原多项式的某几项的乘积。

如果包含了原多项式的乘积,那么在求出卷积之后,

我们发现此时的结果与某个多项式自身的某两项的乘积有关

但是我们在或卷积的结果式中发现一定只与某个多项式的某一项与另一个多项式的某一项有关。

因此,我们知道\(FWT(A)\)的任意一项只与\(A\)的某几项的和有关

因此,我们这个式子可以这样拆开。

此时,这个伪证成立。(这话怎么这么别扭)

但是怎么说,总是感觉证明后面都要贴上一个伪字,

如果我们能够知道\(FWT(A)\)是个啥东西,我们就可以把这个字给扔掉了

先给出结论:

对于\(or\)卷积而言,\(FWT(A)[i]=\sum_{j|i=i}A[j]\)

它基于的原理呢?

如果\(i|k=k,j|k=k\),那么就有\((i|j)|k=k\)

这样说很不清楚,我们现在来证明后半部分为啥是\(FWT(A_0+A_1)\)

因为\(A_0\)中取一项和\(A_1\)中取一项做\(or\)卷积,显然贡献会产生到\(A_1\)中去

首先,对于\(A_1\)中的任意两项的贡献,一定在\(A_1\)中,即使去掉了最高位,此时也会产生这部分的贡献

但是现在在合并\(A_0\)和\(A_1\)的贡献的时候,还需要考虑\(A_0\)的贡献

相当于只丢掉了最高位,因此,在\(A_0\)与\(A_1\)对应项的\(FWT\)的和就是我们现在合并之后的结果

所以也就是\(FWT(A_0+A_1)=FWT(A_0)+FWT(A_1)\)

这样子,我们来考虑或卷积,也就是\(FWT(A|B)\)

我们要证明它等于\(FWT(A)\times FWT(B)\),这样子我们就可以放心的使用\(or\)卷积了

证明:
\[
\begin{aligned}
FWT(A|B)=&FWT((A|B)_0,(A|B)_1)\ =&FWT(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\ =&(FWT(A_0|B_0),FWT(A_0|B_0+A_0|B_1+A_1|B_0+A_1|B_1))\ =&(FWT(A_0)\times FWT(B_0)\\&,FWT(A_0)\times FWT(B_0)+FWT(A_0)\times FWT(B_1)+FWT(A_1)\times FWT(B_0)+FWT(A_1)\times FWT(B_1))\ =&(FWT(A_0)\times FWT(B_0),(FWT(A_0)+FWT(A_1))\times (FWT(B_0)+FWT(B_1)))\ =&(FWT(A_0),FWT(A_0+A_1))\times (FWT(B_0),FWT(B_0+B_1))\ =&FWT(A)\times FWT(B)
\end{aligned}
\]
这是一个数学归纳法的证明,请仔细看一看QwQ

当只有一项的时候这个是显然的。

好啦,这样就证明出了\(or\)卷积的正确性了

4、和(and)卷积

\(and\)卷积是这样的:\(C_k=\sum_{i\&j=k}A_i*B_j\)

写成向量的形式:\(A\&B=(\sum_{i\&j=0}A_i*B_j,\sum_{i\&j=1}A_i*B_j,......)\)

交换律?\(A\&B=B\&A\)显然成立

结合律?和前面一样是满足的。

好的,先把变换的式子写出来。

\[FWT(A)=\begin{cases}(FWT(A_0+A_1),FWT(A_1)) & n\gt0 \\ A & n=0\end{cases}\]

从某种意义上来说,\(and\)和\(or\)和很类似的。

我们这样看:

\(0|0=0,0|1=1,1|0=1,1|1=1\)

\(0\&0=0,0\&1=0,1\&0=0,1\&1=1\)

都是\(3\)个\(0/1\),然后剩下的那个只有一个

既然如此,其实我们也可以用\(or\)卷积类似的东西很容易的证明\(and\)卷积

\(FWT(A+B)=FWT(A)+FWT(B)\)

大致的伪证就是\(FWT(A)\)是关于\(A\)中元素的一个线性组合,显然满足分配率

接着只要再证明

\(FWT(A)\times FWT(B)=FWT(A\&B)\)就行了

方法仍然是数学归纳法。

证明:
\[
\begin{aligned}
FWT(A\&B)=&FWT((A\&B)_0,(A\&B)_1)\\
=&FWT(A_0\&B_0+A_0\&B_1+A_1\&B_0,A_1\&B_1)\\
=&(FWT(A_0\&B_0+A_0\&B_1+A_1\&B_0+A_1\&B_1),FWT(A_1\&B_1))\\
=&((FWT(A_0)+FWT(A_1))\times (FWT(B_0)+FWT(B_1)),FWT(A_1)*FWT(B_1))\\
=&(FWT(A_0+A_1),FWT(A_1))\times (FWT(B_0+B_1),FWT(B_1))\\
=&FWT(A)\times FWT(B)
\end{aligned}
\]
好啦,这样子\(and\)卷积就证明完啦。

5、异或(xor)卷积

为了方便打,我就把异或操作用\(\oplus\)来表示吧(而且这样似乎也是一种常用的表达方式)

主要原因是\(\wedge\)太难打了

表达式:\(C_k=\sum_{i\oplus j=k}A_i*B_j\)

向量式按照上面写就行了

先写一下\(FWT(A)\)吧
\[FWT(A)=\begin{cases}(FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1)) & n>0\\A & n=0\end{cases}\]

\(FWT(A+B)=FWT(A)+FWT(B)\)

这个显然还是成立的,理由和上面是一样的。

接下来还是证明相同的东西

\(FWT(A)\times FWT(B)=FWT(A\oplus B)\)

证明:
\[
\begin{aligned}
FWT(A\oplus B)=&(FWT(A\oplus B)_0+FWT(A\oplus B)_1,FWT(A\oplus B)_0-FWT(A\oplus B)_1)\ =&(FWT(A_0\oplus B_0+A_1\oplus B_1+A_1\oplus B_0+A_0\oplus B_1)\\&,FWT(A_0\oplus B_0+A_1\oplus B_1-A_1\oplus B_0-A_0\oplus B_1))\ =&((FWT(A_0)+FWT(A_1))\times(FWT(B_0)+FWT(B_1))\ &,(FWT(A_0)-FWT(A_1))\times(FWT(B_0)-FWT(B_1))\ =&(FWT(A_0+A_1)\times(B_0+B_1),FWT(A_0-A_1)\times FWT(B_0-B_1))\ =&(FWT(A_0+A_1),FWT(A_0-A_1))\times(FWT(B_0+B_1),FWT(B_0-B_1))\ =&FWT(A)\times FWT(B)
\end{aligned}
\]
好啦好啦

这样子\(xor\)卷积也证明完啦。

于是我们可以开心的来写\(FWT\)啦

6、IFWT

我们现在可以在\(O(nlogn)\)的时间复杂度里面得到\(FWT(A\oplus B)\),其中\(\oplus\)表示一个位运算。

得到了\(FWT\)之后我们需要还原这个数组,也就是\(IFWT\)(叫\(UFWT\)也没啥问题??)

怎么求?

正向的过程我们知道了,逆向的反着做啊。

所以:

\(or\)卷积:\[IFWT(A)=(IFWT(A_0),IFWT(A_1)-IFWT(A_0))\]

\(and\)卷积:\[IFWT(A)=(IFWT(A_0)-IFWT(A_1),IFWT(A_1))\]

\(xor\)卷积:\[IFWT(A)=(\frac{IFWT(A_0)+IFWT(A_1)}{2},\frac{IFWT(A_0)-IFWT(A_1)}{2})\]

7、代码实现

\(or\)卷积的代码

void FWT(ll *P,int opt)
{
    for(int i=2;i<=N;i<<=1)
        for(int p=i>>1,j=0;j<N;j+=i)
            for(int k=j;k<j+p;++k)
                P[k+p]+=P[k]*opt;
}

\(and\)卷积只需要在\(or\)卷积的基础上修改一点点就好了

void FWT(ll *P,int opt)
{
    for(int i=2;i<=N;i<<=1)
        for(int p=i>>1,j=0;j<N;j+=i)
            for(int k=j;k<j+p;++k)
                P[k]+=P[k+p]*opt;
}

\(xor\)卷积其实也差不多(这个是在模意义下的\(FWT\))

如果不是在模意义下的话,开一个\(long\ long\),然后把逆元变成直接除二就好了。

void FWT(int *P,int opt)
{
    for(int i=2;i<=N;i<<=1)
        for(int p=i>>1,j=0;j<N;j+=i)
            for(int k=j;k<j+p;++k)
            {
                int x=P[k],y=P[k+p];
                P[k]=(x+y)%MOD;P[k+p]=(x-y+MOD)%MOD;
                if(opt==-1)P[k]=1ll*P[k]*inv2%MOD,P[k+p]=1ll*P[k+p]*inv2%MOD;
            }
}

原文地址:https://www.cnblogs.com/cjyyb/p/9065615.html

时间: 2024-07-30 18:08:24

FWT快速沃尔什变换学习笔记的相关文章

Qt快速入门学习笔记(基础篇)

本文基于Qter开源社区论坛版主yafeilinux编写的<Qt快速入门系列教程目录>,网址:http://bbs.qter.org/forum.php?mod=viewthread&tid=193.参考书为基于该系列教程<Qt Creator快速入门>和<Qt及Qt Quick开发实战精解> 1.关联Qt库.如果是分别安装的Qt Creator和Qt库,而不是安装集成Qt Creator和Qt库的SDK,则需要手动关联Qt库.打开工具→选项菜单,然后选择“构建

Sass快速入门学习笔记

1. 使用变量; sass让人们受益的一个重要特性就是它为css引入了变量.你可以把反复使用的css属性值 定义成变量,然后通过变量名来引用它们,而无需重复书写这一属性值.或者,对于仅使用过一 次的属性值,你可以赋予其一个易懂的变量名,让人一眼就知道这个属性值的用途. sass使用$符号来标识变量(老版本的sass使用!来标识变量.改成$是多半因为!highlight-color看起来太丑了.),比如$highlight-color和$sidebar-width.为什么选择$ 符号呢?因为它好认

MongoDB快速入门学习笔记3 MongoDB的文档插入操作

1.文档的数据存储格式为BSON,类似于JSON.MongoDB插入数据时会检验数据中是否有“_id”域,如果没有会自动生成.shell操作有insert和save两种方法.当插入一条数据有“_id”值,并且现在集合中已经有相同的值,使用insert插入时插入不进去,使用save时,会更新数据. 1 > db.student.drop() 2 true 3 > db.student.insert({"_id": 1, "name":"zhang

MongoDB快速入门学习笔记1 windows安装MongoDB

1.安装MongoDB 从MongoDB官网上下载MongoDB,我下载的版本是64位的3.2.6.下载完以后直接安装,我的安装目录是D:\work\MongoDB. 2.配置MongoDB的环境变量 在PATH中添加D:\work\MongoDB\Server\3.2\bin.(配置环境变量是为了更加方便的执行bin下的命令) 3.启动MongoDB 启动MongoDB的时候使用mongod命令,--dbpath 设置数据库的路径,--logpath 设置日志文件的路径 mongod --db

MongoDB快速入门学习笔记7 MongoDB的用户管理操作

1.修改启动MongoDB时要求用户验证加参数 --auth 即可.现在我们把MongoDB服务删除,再重新添加服务 mongod --dbpath "D:\work\MongoDB\data" --logpath "D:\work\MongoDB\log\mongodb.log" --install --serviceName "MongoDB" --auth 2.创建用户,并使用创建的用户登录打开shell操作界面,默认test数据,再查看所

MongoDB快速入门学习笔记5 MongoDB的文档修改操作

db.集合名称.update({query},{update},upsert, multi})query:过滤条件update:修改内容upsert:如果不存在查询条件查出的记录,是否插入一条数据,默认是falsemulti:是否只修改查询条件查出的第一条记录,默认是false > db.student.update({_id:1}, {name:"zhang"}) WriteResult({ "nMatched" : 1, "nUpserted&q

MongoDB快速入门学习笔记8 MongoDB的java驱动操作

import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import org.bson.Document; import com.mongodb.MongoClient; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.client.FindIterable;

MongoDB快速入门学习笔记4 MongoDB的文档查询操作

先把student删除,再重新插入数据 > db.student.drop() true > db.student.insert([{ "_id" : 1, "name" : "zhangsan", "age": 27, "sex": 1 }, { "_id" : 2, "name" : "lisi", "age":

编程快速上手 学习笔记(第四章-列表)

1.利用切片取得子列表 spam[2]  是一个裂变和下标(一个证书) spam[1:4] 是一个列表和切片 在一个切片中,从第一个整数下标开始,第二个整数是切片结束的时候的下标,但不包括它 2.len()取列表的长度 3.In和Not in操作符 1 myPets = ['Xiaohong', 'Xiaoming', 'Xiaofang'] 2 print('Enter a pet name: ') 3 name = input() 4 if name not in myPets: 5 pri