我们知道,FFT 和 NTT 可以用来解决下面这种问题:

\[c_k=\sum_{i+j=k}a_ib_j
\]

不过,这并不是卷积的全部形态,比如下面这种:

\[c_k=\sum_{i*j=k}a_ib_j
\]

其中 \(*\) 代表一种位运算。

面对这种位运算类型的卷积,我们也有别样的方法,那就是 FMT 和 FWT。

或卷积

顾名思义,其形态如下:

\[c_k=\sum_{i|j=k}a_ib_j
\]

考虑如下优良性质:

若 \(j|i=i,k|i=i\),则 \((j|k)|i=i\)。

根据这个优良性质,便有了下面两种求解或卷积的方法:

FMT 法

不妨顺着 FFT 的思路,找到 \(FMT(a)_i\times FMT(b)_i=FMT(c)_i\)。

我们构造 \(FMT(a)=\sum\limits_{j|i=i}a_j\),那么就有:

\[FMT(a)_i\times FMT(b)_i=\sum_{j|i=i}a_j\sum_{k|i=i}b_k=\sum_{(j|k)|i=i}a_jb_k=FMT(c)_i
\]

这个时候,FMT 说:“这是高维前缀和。”

于是,问题解决了。直接使用高维前缀和把 \(a\) 和 \(b\) 转成 \(FMT(a)\) 和 \(FMT(b)\),合并后再用高维差分把 \(FMT(c)\) 转为 \(c\)。时间复杂度 \(O(n\log n)\)。

void fmtor(int *a,int fl){
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j]=(a[j]+fl*a[j^(1<<i)])%p;
}

FWT 法

你不要认为 FWT 换了一种构造方式,其实他和 FMT 的构造方式相同。也就是 \(FWT(a)=FMT(a)\)。

不过 FWT 说:“这玩意可以分治。”

于是考虑使用类似 FFT 的方式分治处理。当然,说是分治,但有了 FFT 的经验,写出来的代码是迭代形式的。

设 \(merge(a,b)\) 表示将 \(b\) 序列接到 \(a\) 序列后面,\(a_0\) 表示 \(a\) 序列的左半部分,\(a_1\) 表示 \(a\) 序列的右半部分,那么就有:

\[FWT(a)=merge(FWT(a)_0,FWT(a)_0+FWT(a)_1)
\]

原因非常简单。假如当前枚举的 \(i\) 为 \(0\) 的话,那么你就只能选择 \(0\)(不然或出来的结果会是 \(1\)),而为 \(1\) 的话,这一位就无所谓了。

逆运算就是给后半部分减去前半部分的贡献,即:

\[a=merge(a_0,a_1-a_0)
\]

时间复杂度相同。

void fwtor(int *a,int fl){
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++)
a[l+len+k]=(a[l+len+k]+fl*a[l+k])%p;
}

与卷积

依旧顾名思义,其形态如下:

\[c_k=\sum_{i\&j=k}a_ib_j
\]

同样发现如下优良性质:

若 \(j\&i=i,k\&i=i\),则 \((j\&k)\&i=i\)。

根据这个优良性质,便仍然有下面两种求解与卷积的方法:

FMT 法

我们构造 \(FMT(a)=\sum\limits_{j\&i=i}a_j\),那么就有:

\[FMT(a)_i\times FMT(b)_i=\sum_{j\&i=i}a_j\sum_{k\&i=i}b_k=\sum_{(j\&k)\&i=i}a_jb_k=FMT(c)_i
\]

这个时候,FMT 说:“这是高维后缀和。”

于是,问题再一次解决了。直接使用高维后缀和把 \(a\) 和 \(b\) 转成 \(FMT(a)\) 和 \(FMT(b)\),合并后再用高维差分把 \(FMT(c)\) 转为 \(c\)。时间复杂度 \(O(n\log n)\)。

void fmtand(int *a,int fl){
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j^(1<<i)]=(a[j^(1<<i)]+fl*a[j])%p;
}

FWT 法

FWT 又一次说:“这玩意仍然可以分治。”

观察后发现有:

\[FWT(a)=merge(FWT(a)_0+FWT(a)_1,FWT(a)_1)
\]

逆运算就是给前半部分减去后半部分的贡献,即:

\[a=merge(a_0-a_1,a_1)
\]

时间复杂度相同。

void fwtand(int *a,int fl){
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++)
a[l+k]=(a[l+k]+fl*a[l+len+k])%p;
}

异或卷积

依旧顾名思义,其形态如下:

\[c_k=\sum_{i\oplus j=k}a_ib_j
\]

同样发现如下优良性质:

若 \(j\oplus i=i,k\oplus i=i\),则 \((j\oplus k)\oplus i=i\),且 \(j=k=0\)。

这个性质优良到了我们必须改变思路的程度了,也是太过于优良了。

高维前缀和和高维后缀和都用过了,此时 FMT 并不容易解决这个问题(其实是能解决的,只不过有点牵强),而 \(FWT(a)\) 感觉也相当不好想。

既然异或没有优良性质,我们就要构造一种运算,使它自身有优良性质,且能和异或搭上关系。

我们引入一种新的运算 \(\bullet\),满足 \(x\bullet y=count(x\& y)\bmod 2\),其中 \(count(x)\) 表示 \(x\) 在二进制下 \(1\) 的个数。

那么就有如下优良性质:

\[(x\bullet y)\oplus(x\bullet z)=x\bullet(y\oplus z)
\]

证明:设:

\[S=\{i|2^i\& x>0,2^i\& y>0\}
\]
\[T=\{i|2^i\& x>0,2^i\& z>0\}
\]
\[G=\{i|2^i\& x>0,2^i\& (y\oplus z)>0\}
\]

容易发现:

\[(x\bullet y)\oplus(x\bullet z)=(|S|+|T|)\bmod 2
\]
\[x\bullet(y\oplus z)=|G|\bmod 2
\]

考虑到异或后消掉部分一定在 \(y,z\) 中都存在。换言之,就是:

\[|S|+|T|-2|S\cap T|=|G|
\]
\[(|S|+|T|)\bmod 2=(|G|)\bmod 2
\]
\[(x\bullet y)\oplus(x\bullet z)=x\bullet(y\oplus z)
\]

\(Q.E.D.\)

设 \(FWT(a)_i=\sum\limits_{j\bullet i=0}a_j-\sum\limits_{j\bullet i=1}a_j\),则有:

\[FWT(a)_i\times FWT(b)_i=(\sum\limits_{j\bullet i=0}a_j-\sum\limits_{j\bullet i=1}a_j)(\sum\limits_{k\bullet i=0}b_k-\sum\limits_{k\bullet i=1}b_k)
\]
\[=\sum\limits_{j\bullet i=0}a_j\sum\limits_{k\bullet i=0}b_k-\sum\limits_{j\bullet i=1}a_j\sum\limits_{k\bullet i=0}b_k-\sum\limits_{j\bullet i=0}a_j\sum\limits_{k\bullet i=1}b_k+\sum\limits_{j\bullet i=1}a_j\sum\limits_{k\bullet i=1}b_k
\]
\[=\sum\limits_{(j\bullet i)\oplus(k\bullet i)=0}a_jb_k-\sum\limits_{(j\bullet i)\oplus(k\bullet i)=1}a_jb_k
\]
\[=\sum\limits_{i\bullet(j\oplus k)=0}a_jb_k-\sum\limits_{i\bullet(j\oplus k)=1}a_jb_k=FWT(c)_i
\]

考虑分治方式。当前位 \(i\) 为 \(0\) 时,\(i\bullet j\) 必为 \(0\),所以 \(0,1\) 贡献都为正。同理,\(i=1\) 时,\(0\) 贡献为正,\(1\) 贡献为负。所以有:

\[FWT(a)=merge(FWT(a)_0+FWT(a)_1,FWT(a)_0-FWT(a)_1)
\]
\[a=merge(\frac{a_0+a_1}2,\frac{a_0-a_1}2)
\]

时间复杂度不变。

void fwtxor(int *a,int fl){
fl=(fl==1)?1:(p+1)/2;
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++){
int a0=a[l+k],a1=a[l+len+k];
a[l+k]=(a0+a1)*fl%p;
a[l+len+k]=(a0-a1)*fl%p;
}
}

其实也可以把它写成 FMT 的形式:

void fmtxor(int *a,int fl){
fl=(fl==1)?1:(p+1)/2;
for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if(~j&(1<<i)){
int a0=a[j],a1=a[j|(1<<i)];
a[j|(1<<i)]=(a0-a1)*fl%p;
a[j]=(a0+a1)*fl%p;
}
}

最后放上模板题代码:

//FMT 342ms
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=(1<<17),p=998244353;
int n,f[N],g[N],c[N],a[N],b[N];
void fmtor(int *a,int fl){
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j]=(a[j]+fl*a[j^(1<<i)])%p;
}void fmtand(int *a,int fl){
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++)
if(j&(1<<i)) a[j^(1<<i)]=(a[j^(1<<i)]+fl*a[j])%p;
}void fmtxor(int *a,int fl){
fl=(fl==1)?1:(p+1)/2;
for(int i=0;i<n;i++)
for(int j=0;j<(1<<n);j++)
if(~j&(1<<i)){
int a0=a[j],a1=a[j|(1<<i)];
a[j|(1<<i)]=(a0-a1)*fl%p;
a[j]=(a0+a1)*fl%p;
}
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n;
for(int i=0;i<(1<<n);i++)
cin>>a[i],f[i]=a[i];
for(int i=0;i<(1<<n);i++)
cin>>b[i],g[i]=b[i];
fmtor(f,1),fmtor(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fmtor(c,-1);
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
for(int i=0;i<(1<<n);i++) f[i]=a[i];
for(int i=0;i<(1<<n);i++) g[i]=b[i];
fmtand(f,1),fmtand(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fmtand(c,-1),cout<<"\n";
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
for(int i=0;i<(1<<n);i++) f[i]=a[i];
for(int i=0;i<(1<<n);i++) g[i]=b[i];
fmtxor(f,1),fmtxor(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fmtxor(c,2),cout<<"\n";
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
return 0;
}
//FWT 298ms
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=(1<<17),p=998244353;
int n,f[N],g[N],c[N],a[N],b[N];
void fwtor(int *a,int fl){
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++)
a[l+len+k]=(a[l+len+k]+fl*a[l+k])%p;
}void fwtand(int *a,int fl){
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++)
a[l+k]=(a[l+k]+fl*a[l+len+k])%p;
}void fwtxor(int *a,int fl){
fl=(fl==1)?1:(p+1)/2;
for(int len=1;len<(1<<n);len<<=1)
for(int l=0;l<(1<<n);l+=(len<<1))
for(int k=0;k<len;k++){
int a0=a[l+k],a1=a[l+len+k];
a[l+k]=(a0+a1)*fl%p;
a[l+len+k]=(a0-a1)*fl%p;
}
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n;
for(int i=0;i<(1<<n);i++)
cin>>a[i],f[i]=a[i];
for(int i=0;i<(1<<n);i++)
cin>>b[i],g[i]=b[i];
fwtor(f,1),fwtor(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fwtor(c,-1);
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
for(int i=0;i<(1<<n);i++) f[i]=a[i];
for(int i=0;i<(1<<n);i++) g[i]=b[i];
fwtand(f,1),fwtand(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fwtand(c,-1),cout<<"\n";
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
for(int i=0;i<(1<<n);i++) f[i]=a[i];
for(int i=0;i<(1<<n);i++) g[i]=b[i];
fwtxor(f,1),fwtxor(g,1);
for(int i=0;i<(1<<n);i++)
c[i]=f[i]*g[i]%p;
fwtxor(c,2),cout<<"\n";
for(int i=0;i<(1<<n);i++)
cout<<(c[i]+p)%p<<" ";
return 0;
}

多项式算法再探:FMT 和 FWT的更多相关文章

  1. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  2. 强连通分量再探 By cellur925

    我真的好喜欢图论啊. (虽然可能理解的并不深hhh) 上一次(暑假)我们初探了强联通分量,这一次我们再探.(特别感谢pku-lyc老师的课件.有很多引用) 上次我们忘记讨论复杂度了.tarjan老爷爷 ...

  3. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  4. ViewPager+Fragment再探:和TAB滑动条一起三者结合

    Fragment前篇: <Android Fragment初探:静态Fragment组成Activity> ViewPager前篇: <Android ViewPager初探:让页面 ...

  5. 再探jQuery

    再探jQuery 前言:在使用jQuery的时候发现一些知识点记得并不牢固,因此希望通过总结知识点加深对jQuery的应用,也希望和各位博友共同分享. jQuery是一个JavaScript库,它极大 ...

  6. [老老实实学WCF] 第五篇 再探通信--ClientBase

    老老实实学WCF 第五篇 再探通信--ClientBase 在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创 ...

  7. Spark Streaming揭秘 Day7 再探Job Scheduler

    Spark Streaming揭秘 Day7 再探Job Scheduler 今天,我们对Job Scheduler再进一步深入一下,对一些更加细节的源码进行分析. Job Scheduler启动 在 ...

  8. 再探ASP.NET 5(转载)

    就在最近一段时间,微软又有大动作了,在IDE方面除了给我们发布了Viausl Studio 2013 社区版还发布了全新的Visual Studio 2015 Preview. Visual Stud ...

  9. 再探java基础——break和continue的用法

    再探java基础——break和continue的用法 break break可用于循环和switch...case...语句中. 用于switch...case中: 执行完满足case条件的内容内后 ...

  10. 第四节:SignalR灵魂所在Hub模型及再探聊天室样例

    一. 整体介绍 本节:开始介绍SignalR另外一种通讯模型Hub(中心模型,或者叫集线器模型),它是一种RPC模式,允许客户端和服务器端各自自定义方法并且相互调用,对开发者来说相当友好. 该节包括的 ...

随机推荐

  1. m4 mac mini本地部署ComfyUI,测试Flux-dev-GGUF的workflow模型10步出图,测试AI绘图性能,基于MPS(fp16),优点是能耗小和静音

    m4 mac mini已经发布了一段时间,针对这个产品,更多的是关于性价比的讨论,如果抛开各种补贴不论,价位上和以前发布的mini其实差别不大,真要论性价比,各种windows系统的mini主机的价格 ...

  2. 记一次 .NET某差旅系统 CPU爆高分析

    一:背景 1. 讲故事 前些天训练营里的一位学员找到我,说他们的差旅后台系统出现了CPU爆高的情况,爆高之后就下不去了,自己分析了下也没找到原因,事情比较紧急,让我帮忙看下是什么回事,手里也有dump ...

  3. showModalBottomSheet setState 无法更新ui和高度设置问题

    showModalBottomSheet setState 无法更新ui问题 首先理解showModalBottomSheet,本质上可以理解为路由入栈,在show之后弹出的页面其实是另一个页面了,此 ...

  4. GraphQL Part IV: 浏览器内的 IDE

    只是一个使用,这里不做介绍了.

  5. 技术实践|Redis基础知识及集群搭建(下)

    ​ Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.本篇文章围绕Redis基础知识及集群搭建相关内容进行了分享 ...

  6. 【Vue】Vue项目创建的两种方式

    目录 0.提前准备 (2)webpack (3)vue全局脚手架 查看已安装版本 (4)CNPM 1.创建Vue项目的两种方式 (1)Vue2.x项目 (2)创建Vue3.x项目 (1)使用vue c ...

  7. 第36次ccf-csp题解(思维)

    比赛链接 https://sim.csp.thusaac.com/contest/36/home   比赛感受 这会刚打完上海icpc,比起区域赛的题,这个简单太多了. 感受还不错,写的很顺手.除了第 ...

  8. 小洋的Python入门笔记😀

    小洋的python入门笔记 起因:发现自己前三年做的Python项目很多都是现做先学的,修改理解语法错误多依仗对C/C++的理解,对python缺乏一个系统的学习.趁此有空,补上! 特别鸣谢:B站找到 ...

  9. _findnext()调试中断,发生访问错误,错误定位到ntdll.dll

    问题: 采用_findfirst和_findnext获取指定的文件夹下的文件时,_findnext()函数在调试时发生中断,发生访问错误,错误定位到ntdll.dll.错误提示如下所示: _findn ...

  10. 开源轻量级 IM 框架 MobileIMSDK 的Uniapp客户端库已发布

    一.基本介绍 MobileIMSDK-Uniapp端是一套基于Uniapp跨端框架的即时通讯库: 1)超轻量级.无任何第3方库依赖(开箱即用): 2)纯JS编写.ES6语法.高度提炼,简单易用: 3) ...