FWT 与集合幂级数 学习笔记
FWT
简介
FWT 是用于解决对下标进行位运算卷积问题的方法。
公式:\(c_i= \sum_{i=j \oplus k}a_j b_k\)
其中 \(\oplus\) 是二元位运算中的某一种。
FWT 的原理与 FFT 类似,我们令对数组 \(a\) 经过快速沃尔什变换后记为 \(A\),那么 FWT 的基本原理如下:
1.将 \(a\),\(b\) 变换为 \(A\),\(B\);
2.通过 \(C_i = A_i B_i\) 得到 \(C\)。
3.将 \(C\) 通过逆变换得到 \(c\)。
FWT 的运算
或运算
我们可以构造:\(A_i = \sum_{i \cup j = i}a_j\)。
那么有:\(C_i=A_iB_i\),并且它可以进行快速变换。
考虑分治:对于 \(A\) 进行 FWT,令 \(A\) 的前半为 \(A_0\),后半为 \(A_1\)。
那么对于 \(A_0\),其下标最高位都为 \(0\),对于 \(A_1\),其下标最高位都为 \(1\)。
根据定义,最高为下标为 \(1\) 的位置,可以被下标最高位为 \(0\) 的位置贡献,所以有:
这样就达到了每次操作规模减半的效果,做到时间复杂度 \(O(n \operatorname{log} n)\)。
逆变换就是将操作反过来,公式为 \(a=merge(a_0,a_1-a_0)\)。
这里放个递归版的代码。
inline void solve(int l,int r,int type) {
if (l==r) return out[l]=in[l],void();
int mid=l+r>>1;
solve(l,mid,type);solve(mid+1,r,type);
f(i,mid+1,r) add(out[i],(out[l+i-mid-1]*type+mod)%mod);
}
与运算
与或运算类似,构造 \(A_i = \sum_{i \cup j = j}a_j\)。
同理可得 \(A=merge(A_0+A_1,A_1)\),\(a=merge(a_0-a_1,a_1)\)。
异或运算
我们引入一个新的运算符 \(\circ\),定义 \(x \circ y=popcnt(x \cap y) \operatorname{mod} 2\),即 \(x\) 按位与 \(y\) 二进制下 \(1\) 的个数的奇偶性。(重申一下 \(\oplus\) 表示异或)
我们发现它满足一个重要性质:\((x \circ y) \oplus (x \circ z)=x \circ (y \oplus z)\)。
尝试证明它:
我们将 \(x\),\(y\),\(z\) 看做二进制向量,令 \(x_i\) 表示 \(x\) 的第 \(i\) 位表示的数,其他同理,那么有:
则:
对于每个 \(i\) 我们考虑 \(\oplus_{i=1}(x_iy_i \oplus x_iz_i)\) 与 \(x_i \cdot (y_i \oplus z_i)\) 是否相等。
- 若 \(x_i=0\) 那么两者均为 \(0\)。
- 若 \(x_i=1\) 那么两者均为 \(y_i \oplus z_i\)。
即:\((x \circ y) \oplus (x \circ z)=x \circ (y \oplus z)\),证毕。
同时这个变换同样满足 \(C_i=A_iB_i\),证明如下:
我们考虑如何快速得到 \(A\) 和 \(B\),依然是分治。
同上,对于 \(A\) 分为 \(A_0\) 和 \(A_1\)。
对于最高位为 \(0\),发现其进行 \(\circ\) 运算后结果不变。(因为 \(0\cap 0=0,1\cap 0=0\))
对于最高位为 \(1\),它和 \(0\) 进行 \(\circ\) 运算结果是 \(0\),和 \(1\) 进行 \(\circ\) 运算结果是 \(1\)。
那么可以得出如下公式:
即对于最高位为 \(0\),\(A_0\) 和 \(A_1\) 对它的贡献不会改变。
而对于最高位为 \(1\),\(A_0\) 对它的贡献不变(因为 \(1\cap0=0\)),\(A_1\) 对它的贡献取反(因为 \(1\cap1=1\))。
同理可以得到逆变换:
P4717 【模板】快速莫比乌斯 / 沃尔什变换 (FMT / FWT)
这里贴一下模板题的代码。(迭代实现)
inline void work_or(int *f,int type) {
for (int len=2,mid=1;len<=n;len<<=1,mid<<=1)
for (int i=0;i<n;i+=len) for (int j=0;j<mid;j++)
add(f[i+j+mid],f[i+j]*type);
}
inline void FWT_OR() {
f(i,0,n-1) A[i]=a[i];work_or(A,1);
f(i,0,n-1) B[i]=b[i];work_or(B,1);
f(i,0,n-1) C[i]=1ll*A[i]*B[i]%mod;
work_or(C,-1);
f(i,0,n-1) printf("%d ",C[i]);
putchar(10);
}
inline void work_and(int *f,int type) {
for (int len=2,mid=1;len<=n;len<<=1,mid<<=1)
for (int i=0;i<n;i+=len) for (int j=0;j<mid;j++)
add(f[i+j],f[i+j+mid]*type);
}
inline void FWT_AND() {
f(i,0,n-1) A[i]=a[i];work_and(A,1);
f(i,0,n-1) B[i]=b[i];work_and(B,1);
f(i,0,n-1) C[i]=1ll*A[i]*B[i]%mod;
work_and(C,-1);
f(i,0,n-1) printf("%d ",C[i]);
putchar(10);
}
inline void work_xor(int *f,int type) {
for (int len=2,mid=1;len<=n;len<<=1,mid<<=1)
for (int i=0;i<n;i+=len) for (int j=0;j<mid;j++) {
ll x=f[i+j],y=f[i+j+mid];
f[i+j]=(x+y)*type%mod;
f[i+j+mid]=(x-y+mod)*type%mod;
}
}
inline void FWT_XOR() {
f(i,0,n-1) A[i]=a[i];work_xor(A,1);
f(i,0,n-1) B[i]=b[i];work_xor(B,1);
f(i,0,n-1) C[i]=1ll*A[i]*B[i]%mod;
work_xor(C,998244354/2);
f(i,0,n-1) printf("%d ",C[i]);
putchar(10);
}
多项式的复合
多项式求逆
n^2 做法
令 \(B\) 为 \(A\) 的逆元,根据定义有 \(AB=1\),即
那么有
暴力计算每一项即可。
code
inline void poly_inv(int *f,int *g) {
int inv_f0=qpow(f[0],mod-2);
g[0]=inv_f0;
f(i,1,n-1) {
f(j,1,i) add(g[i],1ll*f[j]*g[i-j]%mod);
g[i]=(mod-1ll*inv_f0*g[i]%mod)%mod;
}
}
多项式 ln
n^2做法
令 \(B(x)=ln A(x)\),求导可得 \(B'(x)=\frac{A'(x)}{A(x)}\),那么有:
一般取 ln 时默认 \(A_0=1\),因此上式也可以写作:
暴力计算每一项即可。
code
inline void poly_ln(int *f,int *g) {
f(i,1,n-1) {
f(j,1,i-1) add(g[i],1ll*j*g[j]%mod*f[i-j]%mod);
g[i]=((f[i]-1ll*qpow(i,mod-2)*g[i]%mod)%mod+mod)%mod;
}
}
多项式 exp
n^2做法
令 \(B(x)=\operatorname{exp}(A(x))\),那么有:
暴力计算每一项即可。
code
inline void poly_exp(int *f,int *g) {
g[0]=1;
f(i,1,n-1) {
f(j,1,i) add(g[i],1ll*j*f[j]%mod*g[i-j]%mod);
g[i]=1ll*qpow(i,mod-2)*g[i]%mod;
}
}
集合幂级数的复合
集合幂级数 exp
定义
设 \(F\) 为满足 \(F(\emptyset)=0\) 的集合幂级数,则它的 exp 定义为 \(exp(F)=\sum_{k\ge 0}\frac{F^{k}}{k!}\),其中 \(F^k\) 为 \(F\) 的 \(k\) 次子集卷积。
算法
核心思想:
- 将集合幂级数 \(F(S)\) 视为一个关于集合大小 \(|S|\) 的多项式:\(F_S(y)=F(S) \cdot y^{|S|}\)。
- 对每个集合 \(S\) 做 FMT 得到 \(\hat F_S(y)\)。
- 对于每个 \(S\),计算多项式 \(\operatorname{exp}({\hat F_S(y)})\)。
- 对结果做 IFMT 即可得到 \(\operatorname{exp}(F(S))\)。
具体地,对于集合幂级数 \(f\) 和 \(g\) 满足 \(g=\operatorname{exp}f\),那么对 \(f\) 进行 FMT 后可以得到:
推导如下:
并且要求 \(T_i\) 两两无交。
做 FMT 可得:
也就是说对固定每个 \(k\),对 \(f_k\) 做 FMT,再在每个 \(S\) 上做多项式 exp,贡献到相应的 \(g_k\),最后对每个 \(g_k\) 做 IFMT 即可。
code
f(i,0,S-1) g[0][i]=1;
f(i,0,n) FWT(f[i],1);
f(i,1,n) {
f(j,1,i) f(k,0,S-1) add(g[i][k],1ll*j*f[j][k]%mod*g[i-j][k]%mod);
f(j,0,S-1) g[i][j]=1ll*g[i][j]*inv[i]%mod;
}
f(i,0,n) FWT(g[i],-1);
集合幂级数 ln 和 集合幂级数求逆
算法
类似于集合幂级数 exp,把其中的多项式 exp 换成多项式 ln 或多项式求逆即可,在这里不多赘述。
code
集合幂级数 ln:
f(i,0,n) FWT(f[i],1);
f(i,1,n) {
f(j,1,i-1) f(k,1,S-1) add(g[i][k],1ll*j*g[j][k]%mod*f[i-j][k]%mod);
f(j,0,S-1) g[i][j]=((f[i][j]-1ll*inv[i]*g[i][j]%mod)%mod+mod)%mod;
}
f(i,0,n) FWT(g[i],-1);
集合幂级数求逆:
f(i,0,n) FWT(f[i],1);
f(k,0,S-1) {
int inv_f0=qpow(f[0][k],mod-2);g[0][k]=inv_f0;
f(i,1,n) {
f(j,1,i) add(g[i][k],1ll*f[j][k]*g[i-j][k]%mod);
g[i][k]=1ll*inv_f0*g[i][k]%mod;
g[i][k]=(mod-g[i][k])%mod;
}
}
f(i,0,n) FWT(g[i],-1);
集合幂级数 exp 和集合幂级数 ln 的组合意义
对于集合幂级数 \(f\),\(g\) 满足 \(g=\operatorname{exp}(f)\),\(f=\operatorname{ln}(g)\),\(f_s\) 表示为集合 \(s\) 的权值,那么 \(g_s\) 则表示对于 \(s\) 所有划分的权值之和,一个划分的权值定义为所有划分出来的集合权值之积。
公式表达为:
其中 \(\Pi(S)\) 表示 \(S\) 的所有划分的集合。
总结起来就是:exp 是从连通结构到一般结构,ln 是从一般结构中提取连通成分。
集合幂级数在子图问题上的应用
有向图相关
数 DAG 定向
给定无向图,求给边定向使得其是 DAG 的方案数。
对于一张 DAG,一个明显的性质就是存在拓扑排序,于是从这里入手,我们每次删去无出度(或无入度)结点,就能递归到子问题。
但是有一个明显的缺点就是:如果只考虑一个无出度点,由于拓扑排序个数不唯一,那么删点的顺序也是不唯一的。所以我们考虑每次删去所有无出度点,转化成一个子问题解决。
回到题目,令 \(dp_s\) 表示导出子图为 \(s\) 时,上述问题的答案。
我们每次枚举无出度点集,需要保证它是独立集,可以得到转移方程:\(dp_s \leftarrow dp_{s \setminus t}\),\(t \subseteq s\) 且 \(t\) 为独立集。
这个转移会出错,因为它在转移时并没有保证除去 \(t\) 后剩余的点集都有出度,导致记重。具体的,若一个集合 \(s\) 内的点没有出度,那么对于任意 \(t \subseteq s \land t \ne \emptyset\) 的 \(t\) 都会造成一次贡献,即造成 \(2^{|s|}-1\) 次贡献,但是实际上应该只有一次贡献。
考虑容斥,对于 \(dp_t\) 配上 \((-1)^{|t|+1}\) 的容斥系数,那么对于每一个无环定向,令其无出度点集为 \(s\),则会被计入 \(\sum_{t\subseteq s \land t \ne \emptyset} (-1)^{|T|+1}=1\)(由组合数学可以推得),这样就解决了数重的问题。
转移方程 \(dp_s \leftarrow (-1)^{|t|+1}dp_{s \setminus t}\)。
直接 dp 是 \(O(n^3)\) 的,考虑多项式优化。
令 \(g_S=\sum_{T \subseteq S}(-1)^{|t|+1}[T是独立集]\),那么 \(f = f \ * g+1\),得到 \(f=\frac{1}{1-g}\),用集合幂级数求逆可以做到 \(O(n^22^n)\)。
在对 DAG 的容斥中,我们可以考虑如何配系数使得合法方案正好被计入 \(1\) 次,非法方案正好被计入 \(0\) 次,即需要做到不重不漏不记错,这种技巧被称为“DAG 容斥”。
相关题目:
P6846 [CEOI 2019] Amusement Park
AT_abc306_h [ABC306Ex] Balance Scale
参考资料
国家集训队2015论文集,集合幂级数的性质与应用及其快速算法,吕凯风
apio2025,集合幂级数在子图计数问题上的应用,cxy

浙公网安备 33010602011771号