FWT求解的是一类问题:\( a[i] = \sum\limits_{j\bigoplus k=i}^{} b[j]*c[k] \)

其中,\( \bigoplus \) 可以是 or,and,xor

三种问题的解决思路都是对多项式 \( a \) 构造一个 \( a' \),令 \( a' = b' * c' \);

那么只需要把 \( b \) 变换成 \( b' \),\( c \) 变换成 \( c' \),然后乘出 \( a' \),再逆变换得到 \( a \);

下面问题就变成如何快速(logn)求 \( b \) 到 \( b' \) 的变换,这个变换就是 FWT;

始终要记住进行位运算的是位置(角标)而不是值;

一、or

构造 \( a'[i] = \sum\limits_{j|i=i}^{} a[j] \)

1.正变换

考虑把 \( a \) 分成前后两个部分 \( a0 \) 和 \( a1 \),先分别递归下去做好,得到 \( a0' \) 和 \( a1' \);

可以发现,\( a0' \) 和 \( a1' \) 的位置(角标)数字上唯一不同就是最高位是0或1;

但递归下去做的时候,\( a0' \) 和 \( a1' \) 的位置数字相当与去掉了最高位(因为折半了);

所以合并的时候,关键要考虑到最高位的0和1的不同:

(1) 对于 \( a' \) 的一个位置 \( i \) ,如果它在前半部分,那么它可以直接继承 \( a0'[i] \);

而 \( a1'[i]\) 由于实际上 \( i \) 还应该加上最高位的1,or 运算使它能贡献的位置最高位也是1,但 \( i \) 的最高位是0,所以不贡献给 \( a'[i] \) ;

(2)对于后半部分的 \( i \) ,\( a0'[i] \) 和 \( a1'[i] \) 都会对它产生贡献,因为两部分的位置数字都是 \( i \) 的子集;

所以可以得到:\( a' = \left ( a0' , a0'+a1' \right ) \)

递归的底层,只有一个元素的时候,\( a = a' \) ,于是我们可以递归做出正变换了;

当然,仿照 FFT 的写法即可,并不需要真的写递归函数,而且也不用蝴蝶变换;

2.逆变换

同样先考虑两个部分 \( a'0 \) 和 \( a'1 \) ,表示 \( a' \) 的前后部分;

已经做了 \( a' = \left ( a0' , a0'+a1' \right ) \)

现在要从 \( a' \) 拆出 \( a0' \) 和 \( a1' \)

那么 \( a0' = a'0 \)

而且 \( a1' = a'1 - a'0 \)

知道了 \( a0' \) 和 \( a0' \) ,就可以继续递归求解 \( a0 \) 和 \( a1 \),二者合起来就可以得到 \( a \)

递归的底层,只有一个元素的时候,\( a' = a \) ,于是我们可以递归作出逆变换了;

void fwt1(int *a,int tp)//a'=(a0',a0'+a1')  //a=(a0',a1'-a0')
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
a[j+mid+k]=upt(a[j+mid+k]+tp*a[j+k]);
}

or

二、and

构造 \( a' = \sum\limits_{j \& i=i}^{} a[j] \)

1.正变换

和 or 同理,考虑最高位01的不同,后面继承本身,而前面要加上后面的贡献;

得到 \( a' = \left ( a0'+a1' , a1' \right ) \)

2.逆变换

同理,得到

\( a0' = a'0 - a'1 \)

\( a1' = a'1 \)

void fwt2(int *a,int tp)//a'=(a0'+a1',a1')  //a=(a0'-a1',a1')
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
a[j+k]=upt(a[j+k]+tp*a[j+mid+k]);
}

and

三、xor

设 \( d(i,j) \) 表示 \( i\&j \) 二进制表示中1的个数;

构造 \( a' = \sum\limits_{d(i,j)\%2==0}^{} a[j] - \sum\limits_{d(i,j)\%2==1}^{} a[j] \)

1.正变换

让我们三步走:

(1) \( a' = \left ( a0' + a1' , a0' - a1' \right ) \)

首先明确,\( a' \) 是 \( d(i\&j) \) 为偶数的 \( a[j] \) 求和,减去 \( d(i\&j) \) 为奇数的 \( a[j] \) 求和;

<1> 对于整体的一个位置 \( i \),它在前半部分

对于前半部分(折半)的相同位置 \( i' \),在前半部分的 \( j \) 中,\( d(i'\&j) \) 的奇偶性和 \( d(i\&j) \) 一样,所以继承答案;

对于后半部分(折半)的相同位置 \( i' \),在后半部分的 \( j \) 中,计算 \( i'\&j \) 时是没有考虑最高位的,所以它们的最高位上都是0,

而因为 \( i \) 的最高位是0,\( i\&j \) 的最高位同样是0,所以正好符合,答案可以加上;

也就是,\( a' = \left ( a0' + a1' , ... \right ) \)

<2> 对于整体的一个位置 \( i \),它在后半部分

对于前半部分(折半)的相同位置 \( i' \),在前半部分的 \( j \) 中,\( d(i'\&j) \) 的最高位都是0,

而因为 \( j \) 的最高位是0,\( i\&j \) 的最高位同样是0,所以正好符合,答案可以加上;

对于后半部分(折半)的相同位置 \( i' \),在后半部分的 \( j \) 中,计算 \( i'\&j \) 时是没有考虑最高位的,所以它们的最高位上都是0,

但 \( i\&j \) 的最高位是1,所以奇偶性都反了,答案加上的是负的;

这样,就得到 \( a' = \left ( a0' + a1' , a0' - a1' \right ) \)

(2) \( d(i\&k) \otimes d(j\&k) = d( (i \otimes j)\&k ) \)

因为是 \( \& \) ,我们就看 \( k \) 是1的那些位;

如果 \( d(i\&k) \) 是偶数,说明 \( i\&k \) 有偶数个1和 \( k \) 重合,奇数同理,\( j \) 同理;

<1> 当 \( d(i\&k) \) 和 \( d(j\&k) \) 奇偶性相同时

\( d(i\&k) + d(j\&k) \) 是偶数;

而 \( i \otimes j \) 同时消去 \( i \) 和 \( j \) 相同位置的1,不是 \( k \) 的1就算了,是 \( k \) 的1,消去的也是偶数;

所以 \( d( (i \otimes j)\&k ) \) 是偶数;

<2> 当 \( d(i\&k) \) 和 \( d(j\&k) \) 奇偶性不同时

\( d(i\&k) + d(j\&k) \) 是奇数;

而 \( i \otimes j \) 同时消去 \( i \) 和 \( j \) 相同位置的1,不是 \( k \) 的1就算了,是 \( k \) 的1,消去的是偶数;

所以 \( d( (i \otimes j)\&k ) \) 是奇数;

这样我们就证明了 \( d(i\&k) \otimes d(j\&k) = d( (i \otimes j)\&k ) \)

(3) 若 \( c[i] = \sum_{j \otimes k=i}^{} a[j]*b[k] \) ,有 \( c' = a' * b' \)

因为 \( c[i] = \sum_{j \otimes k=i}^{} a[j]*b[k] \)

又 \( c' = \sum_{d(i,j)\%2==0}^{} c[j] - \sum_{d(i,j)\%2==1}^{} c[j] \)

代入,得到 \( c'[i] = \sum_{d((j \otimes k)\&i)\%2==0}^{} a[j]*b[k] - \sum_{d((j \otimes k)\&i)\%2==1}^{} a[j]*b[k] \)

而 \( a'[i] * b'[i] = ( \sum_{d(i,j)\%2==0}^{} a[j] - \sum_{d(i,j)\%2==1}^{} a[j]) * ( \sum_{d(i,j)\%2==0}^{} b[j] - \sum_{d(i,j)\%2==1}^{} b[j]) \)

拆开再组合,并使用(2)得到的结论,就得到 \( a'[i] * b'[i] = ( \sum_{d((j \otimes k)\&i)\%2==0}^{} a[j]*b[k] - \sum_{d((j \otimes k)\&i))\%2==1}^{} a[j]*b[k]) \)

所以 \( c' = a' * b' \)

综上,我们仍然可以递归求 xor 的正变换,\( a' = \left ( a0' + a1' , a0' - a1' \right ) \)

2.逆变换

根据正变换就可以知道咯:

\( a0' = (a'0 + a'1) / 2 \)

\( a1' = (a'0 - a'1) / 2 \)

void fwt3(int *a,int tp)//a'=(a0'+a1',a0'-a1')  //a=((a0'+a1')/2,(a0'-a1')/2)
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
{
int x=a[j+k],y=a[j+mid+k];
a[j+k]=upt(x+y); a[j+mid+k]=upt(x-y);
if(tp==-)a[j+k]=(ll)a[j+k]*inv%mod,a[j+mid+k]=(ll)a[j+mid+k]*inv%mod;
}
}

xor

看例题:https://www.luogu.org/problemnew/show/P4717

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int const xn=(<<),mod=;
int n,a[xn],b[xn],f[xn],g[xn],lim,inv;
int rd()
{
int ret=,f=; char ch=getchar();
while(ch<''||ch>''){if(ch=='')f=; ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return f?ret:-ret;
}
ll pw(ll a,int b)
{
ll ret=;
for(;b;b>>=,a=(a*a)%mod)
if(b&)ret=(ret*a)%mod;
return ret;
}
int upt(int x){while(x>=mod)x-=mod; while(x<)x+=mod; return x;}
void fwt1(int *a,int tp)//a'=(a0',a0'+a1') //a=(a0',a1'-a0')
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
a[j+mid+k]=upt(a[j+mid+k]+tp*a[j+k]);
}
void fwt2(int *a,int tp)//a'=(a0'+a1',a1') //a=(a0'-a1',a1')
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
a[j+k]=upt(a[j+k]+tp*a[j+mid+k]);
}
void fwt3(int *a,int tp)//a'=(a0'+a1',a0'-a1') //a=((a0'+a1')/2,(a0'-a1')/2)
{
for(int mid=;mid<lim;mid<<=)
for(int j=,len=(mid<<);j<lim;j+=len)
for(int k=;k<mid;k++)
{
int x=a[j+k],y=a[j+mid+k];
a[j+k]=upt(x+y); a[j+mid+k]=upt(x-y);
if(tp==-)a[j+k]=(ll)a[j+k]*inv%mod,a[j+mid+k]=(ll)a[j+mid+k]*inv%mod;
}
}
int main()
{
n=rd(); lim=(<<n); inv=pw(,mod-);
for(int i=;i<lim;i++)a[i]=f[i]=rd();
for(int i=;i<lim;i++)b[i]=g[i]=rd();
fwt1(f,); fwt1(g,);
for(int i=;i<lim;i++)f[i]=(ll)f[i]*g[i]%mod;
fwt1(f,-);
for(int i=;i<lim;i++)printf("%d ",f[i]); puts(""); for(int i=;i<lim;i++)f[i]=a[i],g[i]=b[i];
fwt2(f,); fwt2(g,);
for(int i=;i<lim;i++)f[i]=(ll)f[i]*g[i]%mod;
fwt2(f,-);
for(int i=;i<lim;i++)printf("%d ",f[i]); puts(""); for(int i=;i<lim;i++)f[i]=a[i],g[i]=b[i];
fwt3(f,); fwt3(g,);
for(int i=;i<lim;i++)f[i]=(ll)f[i]*g[i]%mod;
fwt3(f,-);
for(int i=;i<lim;i++)printf("%d ",f[i]); puts("");
return ;
}

参考博客:https://www.cnblogs.com/ACMLCZH/p/8022502.html

https://blog.csdn.net/neither_nor/article/details/60335099

快速沃尔什变换(FWT)学习笔记 + 洛谷P4717 [模板]的更多相关文章

  1. 一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记

    一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记 曾经某个下午我以为我会了FWT,结果现在一丁点也想不起来了--看来"学"完新东西不经常做题不写博客,就白学了 = = 我没啥智 ...

  2. 快速沃尔什变换 (FWT)学习笔记

    证明均来自xht37 的洛谷博客 作用 在 \(OI\) 中,\(FWT\) 是用于解决对下标进行位运算卷积问题的方法. \(c_{i}=\sum_{i=j \oplus k} a_{j} b_{k} ...

  3. 快速沃尔什变换 FWT 学习笔记【多项式】

    〇.前言 之前看到异或就担心是 FWT,然后才开始想别的. 这次学了 FWT 以后,以后判断应该就很快了吧? 参考资料 FWT 详解 知识点 by neither_nor 集训队论文 2015 集合幂 ...

  4. 斜率优化dp学习笔记 洛谷P3915[HNOI2008]玩具装箱toy

    本文为原创??? 作者写这篇文章的时候刚刚初一毕业…… 如有错误请各位大佬指正 从例题入手 洛谷P3915[HNOI2008]玩具装箱toy Step0:读题 Q:暴力? 如果您学习过dp 不难推出d ...

  5. 边带权并查集 学习笔记 & 洛谷P1196 [NOI2002] 银河英雄传说 题解

    花了2h总算把边带权并查集整明白了qaq 1.边带权并查集的用途 众所周知,并查集擅长维护与可传递关系有关的信息.然而我们有时会发现并查集所维护的信息不够用,这时"边带权并查集"就 ...

  6. STL Stack(栈)学习笔记 + 洛谷 P1449 后缀表达式

    稍微看了看刘汝佳的白皮书,“实用主义”的STL实在是香到我了,而且在实验室大佬的推荐下我开始了stl的学习. 每篇附带一个题目方便理解,那行,直接开始. 毕竟是实用主义,所以就按照给的题目的例子来理解 ...

  7. STL Queue(队列)学习笔记 + 洛谷 P1540 机器翻译

    队(Queue) 队简单来说就是一个先进先出的“栈”,但是不同于标准“栈”的先进后出. 基本操作: push(x) 将x压入队列的末端 pop() 弹出队列的第一个元素(队顶元素),注意此函数并不返回 ...

  8. 【学习笔鸡】快速沃尔什变换FWT

    [学习笔鸡]快速沃尔什变换FWT OR的FWT 快速解决: \[ C[i]=\sum_{j|k=i} A[j]B[k] \] FWT使得我们 \[ FWT(C)=FWT(A)*FWT(B) \] 其中 ...

  9. FMT/FWT学习笔记

    目录 FMT/FWT学习笔记 FMT 快速莫比乌斯变换 OR卷积 AND卷积 快速沃尔什变换(FWT/XOR卷积) FMT/FWT学习笔记 FMT/FWT是算法竞赛中求or/and/xor卷积的算法, ...

随机推荐

  1. VMware安装黑群暉5.2

      选择典型就可以了,点击下一步. 选择 稍后安装操作系统,点击下一步. 客户机操作系统选择Linux,版本选择其他Linux2.6.x内核64位, 填写虚拟机名称和虚拟机文件保存位置的.填写好后点击 ...

  2. 改进的SMS4算法的差分故障与暴力联合攻击

    改进的SMS4算法的差分故障与暴力联合攻击 (1.中国科学院研究生院,北京100049) 摘要SMS4是在国内正式使用并于2006年发布的第一个用于无线局域网的商用分组password算法.文中研究了 ...

  3. Eclipse出现&quot;Running Android Lint has encountered a problem&quot;解决方式

    近期打开Eclipse的时候,总是发生这种一个错误:"Running Android Lint has encountered a problem".截图例如以下: . 可是Ecl ...

  4. 找top 10信息

    本文章内容来源于<程序猿面试宝典>. 题目: 有1千万条短信,以文本文件的形式保存.一行一条,有反复.请用5分钟时间,找出反复出现最多的前10条. 解析: 某些面试者想用数据库的办法来实现 ...

  5. 解读Unity中的CG编写Shader系列1——初识CG

    CG=C for Graphics  用于计算机图形编程的C语言超集 前提知识点: 1.CG代码必须用 CGPROGRAM ... ENDCG括起来 2.顶点着色器与片段着色器的主函数名称可任意,但须 ...

  6. Cocos2d-x 避免手工输入项目需要编译的cpp文件到Android.mk里

    手工输入项目需要编译的cpp文件到Android.mk里的缺点 1)繁琐,如果cpp文件很多,简直无法忍受 2)手工输入过程中容易出现错误 3)如果cpp文件更改名称,需要修改Android.mk文件 ...

  7. eclipse从svn检出项目

    在eclipse的project explorer 右键->import->svn->从svn检出项目,然后填写资源库的位置,完成,然后一直next. 直到项目检出完成后,选择项目, ...

  8. kubectl技巧之查看资源列表,资源版本和资源schema配置

    系列目录 在kubernetes里,pod,service,rs,rc,deploy,resource等对象都需要使用yaml文件来创建,很多时候我们都是参照照官方示例或者一些第三方示例来编写yaml ...

  9. kubernetes高级之创建只读文件系统以及只读asp.net core容器

    系列目录 使用docker创建只读文件系统 容器化部署对应用的运维带来了极大的方便,同时也带来一些新的安全问题需要考虑.比如黑客入侵到容器内,对容器内的系统级别或者应用级别文件进行修改,会造成难以估量 ...

  10. caffe学习--Lenet5的应用和原理、实现----ubuntu16.04.2+caffe+mnist+train+test

    Lenet5的应用和原理.实现 ----------------------------------------------ubuntu16.04.2------------------------- ...