这是属于补锅计划的一部分。
那就直接进入正题。
理论基础
用于解决求解 A⊕B 的方法,其中(⊕ 是二进制运算)
名字很高级 快速莫比乌斯/沃尔什变换 (FMT/FWT)
,其实基本思想比较简单。
假设有一个序列 A ,通过某种变换我们可以得到另一个序列 fwt[A] ,如果对于整个序列 A 复杂度为 O(n2) 的计算可以等价于在 fwt[A] 中的 O(n) 的计算,那么只要这种变换的复杂度很低,那么整个过程的复杂度就降下来了。
比如卷积,两个函数的的卷积我们不好求,但我们可以用 O(nlogn) 的复杂度求出他的点值,而点值的运算等价于原函数的计算,那么整个FFT的复杂度就是 O(nlogn) 的。
具体去解释这个变换比较复杂,推荐先通过模板题题解区了解具体操作再来学习理论。
这里就挑其中的一些重点,省去了一些证明,全面的解释见这 。
-
FWT 是一个线性变换 ,即你可以理解为 A 是一个 n 维向量,变换是一个 n×n 的矩阵 C,fwt[A]i=n−1∑j=0AjCi,j 。
-
因为 ⊕ 是二进制运算,所以我们可以把每一位分开考虑(按位拆半)。
设 A0 为幂级数下标首位为 0 的部分,类似地有 A1。
-
若 i0=0 ,则有 :
fwt[A]i=c(0,0)fwt[A0]i+c(0,1)fwt[A1]i(i∈[0,n/2))
-
若 i0=1 ,则有 :
fwt[A]i+(n/2)=c(1,0)fwt[A0]i+c(1,1)fwt[A1]i(i∈[0,n/2))
所以对于 C 矩阵来说,有用的只有 c([0,1],[0,1]) 。
-
fwt[A] 变换回 A 就是通过 C 的逆矩阵得到,因为 A∗C=fwt[A]→fwt[A]∗C−1=A 。
模板
或
即求解 ci=∑j|k=iajbk 。
-
矩阵: [1011] 。
即 fwt[A]i=fwt[A0]ifwt[A]i+(n/2)=fwt[A0]i+fwt[A1]i 。
c(i,j)=[i|j=i] 。
含义:子集求和(高维前缀和)
-
逆矩阵: [10−11] 。
即 fwt[A]i=fwt[A0]ifwt[A]i+(n/2)=−fwt[A0]i+fwt[A1]i 。
c(i,j)=−[i|j=i] 。
含义:高维前缀差。
与
即求解 ci=∑j&k=iajbk 。
-
矩阵: [1101] 。
即 fwt[A]i=fwt[A0]i+fwt[A1]ifwt[A]i+(n/2)=fwt[A1]i 。
c(i,j)=[i&j=i] 。
含义:超集求和(高维后缀和)
-
逆矩阵: [1−101] 。
即 fwt[A]i=fwt[A0]i−fwt[A1]ifwt[A]i+(n/2)=fwt[A1]i 。
c(i,j)=−[i&j=i] 。
含义:高维后缀差。
异或
即求解 ci=∑jxork=iajbk 。
-
矩阵: [111−1] 。
即 fwt[A]i=fwt[A0]i+fwt[A1]ifwt[A]i+(n/2)=fwt[A1]i−fwt[A1]i 。
c(i,j)=(−1)|i&j| 。
含义:不知道...
-
逆矩阵: [0.50.50.5−0.5] 。
即 fwt[A]i=12(fwt[A0]i+fwt[A1]i)fwt[A]i+(n/2)=12(fwt[A1]i−fwt[A1]i) 。
c(i,j)=12n(−1)|i&j| 。
代码,复杂度是 O(nlogn) 的。
例题
因为 n 很小,我们可以暴力考虑每一行翻不翻转。
对应的看列状态,设 ai 表示 i 这种状态的出现次数,bi 表示二进制数 i 的 0/1 较小值。
那么当枚举的行翻转状态为 x ,那么 ans=∑2ni=0aibixorx 。
变一下就有 ans=∑ixorj=xaibj ,那么我们就可以用 xor 卷积一次求出所有行翻转状态的答案,最后取 min 就是答案。
如果没有 i&j=0 这是好做的。
注意到 i|j=s,i&j=0 等价于 i|j=s,|i|+|j|=|s| 。
那么我们可以把单个的值换成是一个多项式,把上面提到的加法、乘法都转换成多项式的加法、乘法,之后就是一模一样的了。
复杂度是 O(nlog2n) 的。
实现上可以把多项式维度转移到前面,可以结合代码理解。
还有一个形如 S[s]=C[s]∑t⊊sS[t]W[s−t] 这样的dp也可以通过子集卷积来优化。
题目:P4221 [WC2018]州区划分 。
没有什么特别重要的,就直接给题解链接了
还有一些板子,就不说了。
高级运用
复杂理论的用处来了。
因为这题是要求一堆数与起来为 0 ,和我们上面所处理的两个数异或起来不同,这很像几个多项式乘在一起。
那么我们就可以构造多项式 Fi,其中 Fi[m]=Fi[ai]=1,m=2n−1 这样在 于卷积 意义下分别表示 不选、选 ai 。
那么答案就是 F(x)=[x0]∏iFi(x) ,但暴力乘起来的复杂度是 O(n2logn) 的。
每个多项式都只有两个非 0 项,我们来寻找一下规律:
设 c(i,j) 为and卷积的变换系数,则 fwt[Fk]i=m∑j=0c(i,j)Fk[j]=c(i,m)+c(i,ak) 。
经过我们上面的分析,c(i,j)=[i&j=i] ,那么我们就知道:
fwt[Fk]i={2i&ak=i1i&ak≠i
因为 fwt[F(x)]i=n∏k=1fwt[Fk]i ,那么我们只要知道第 i 位有多少个 2 ,那么 fwt[F(x)]i 就等于 2 的多少次方。相当于对每个 i 求有多少个 ak&i=i ,就是或卷积。
求出来之后直接再逆操作转成 F(x) 即可。
仿照上题,我们设 Fk(x) ,其中 Fk[ak]=x,Fk[bk]=y,Fk[ck]=z ,答案为 [xi]∏Fk(x) 。
同样 fwt[Fk]i=c(i,ak)x+c(i,bk)y+c(i,ck)z ,然后有 c(i,j)=(−1)|i&j| 。
那么 fwt[F(x)]i=n∏k=1fwt[Fk]i=n∏k=1(−1)|i&ak|x+(−1)|i&bk|y+(−1)|i&ck|z 。
到了这里,发现我们无法适用上面的解法了,因为后面的式子比较复杂。
但是因为 (−1)|i&ak|x+(−1)|i&bk|y+(−1)|i&ck|z 只有 8 种取值,如果我们分别得到其个数,就可以用快速幂得到答案。
8 种还是太多了,有一种巧妙的化简方法 :
由异或的自反性,每个把三元组 {ai,bi,ci} 异或上 ai ,变为 {0,bi⊕ai,ci⊕ai}。
那么最后的结果异或上 ⊕ni=1ai 就能得到原来的答案。
所以就变成了 4 种。
对于 i 来说,我们设 c1,c2,c3,c4 分别表示 x+y+z,x+y−z,x−y+z,x−y−z 的个数。
那么 c1=n∑k=1[(−1)|i&bk|=1∧(−1)|i&ck|=1] ,同样可以得到 c2,c3,c4 的含义。
发现这和异或情况下的 c(i,j) 的系数很相似。
我们可以令 Fk[bk]=1 ,即 x=0,y=1,z=0 ,那么此时 fwt[Fk]i=(−1)|i&bk| ,于是 n∑k=1fwt[Fk]i=p1=c1+c2−c3−c4 。
怎么求 n∑k=1fwt[Fk]i ,我们有 fwt(F+G)=fwt(F)+fwt(G) ,因为这东西是一个线性变换,其实就是向量乘矩阵 ,那么我们计算 fwt(n∑k=1Fk) 就可以了。
同样的,还有 令 Fk[ck]=1 ,此时 n∑k=1fwt[Fk]i=p1=c1−c2+c3−c4 。
令 Fk[bk]=Fk[ck]=1 ,此时 n∑k=1fwt[Fk]i=p1=c1−c2−c3+c4 。
还有 c1+c2+c3+c4=n ,那么就可以解方程解出 c1,c2,c3,c4 的值了。
最后像上题一样逆操作转成 F(x) 即可。
和多项式高级操作的结合
没想到吧,这玩意还可以求逆、ln、exp,小编也没想到,所以这部分就咕咕咕了。
到时候再补吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现