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\) 的位置贡献,所以有:

\[A=merge(A_0,A_0+A_1) \]

这样就达到了每次操作规模减半的效果,做到时间复杂度 \(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\) 位表示的数,其他同理,那么有:

\[x \circ y=\oplus_{i=1}(x_iy_i) \]

则:

\[(x \circ y) \oplus (x \circ z)=(\oplus_{i=1}x_iy_i) \oplus (\oplus_{i=1}x_iz_i)=\oplus_{i=1}(x_iy_i \oplus x_iz_i) \]

\[x \circ (y \oplus z) = x_i \cdot (y_i \oplus z_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\),证明如下:

\[\begin{aligned} A_iB_i &= (\sum_{i \circ j=0}a_j-\sum_{i \circ j=1}a_j) (\sum_{i \circ k=0}b_k-\sum_{i \circ k=1}b_k)\\ &=(\sum_{i \circ j=0}a_j\sum_{i \circ k=0}b_k +\sum_{i \circ j=1}a_j\sum_{i \circ k=1}b_k) -(\sum_{i \circ j=0}a_j\sum_{i \circ k=1}b_k+ \sum_{i \circ j=1}a_j\sum_{i \circ k=0}b_k)\\ &=\sum_{(j \oplus k)\circ i=0}a_jb_k-\sum_{(j \oplus k)\circ i=1}a_jb_k\\ &=C_i \end{aligned} \]

我们考虑如何快速得到 \(A\)\(B\),依然是分治。

同上,对于 \(A\) 分为 \(A_0\)\(A_1\)

对于最高位为 \(0\),发现其进行 \(\circ\) 运算后结果不变。(因为 \(0\cap 0=0,1\cap 0=0\)

对于最高位为 \(1\),它和 \(0\) 进行 \(\circ\) 运算结果是 \(0\),和 \(1\) 进行 \(\circ\) 运算结果是 \(1\)

那么可以得出如下公式:

\[A=merge(A_0+A_1,A_0-A_1) \]

即对于最高位为 \(0\)\(A_0\)\(A_1\) 对它的贡献不会改变。

而对于最高位为 \(1\)\(A_0\) 对它的贡献不变(因为 \(1\cap0=0\)),\(A_1\) 对它的贡献取反(因为 \(1\cap1=1\))。

同理可以得到逆变换:

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

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\),即

\[\sum_{i=0}^n A_iB_{n-i}=[n=0] \]

那么有

\[B_n=-\frac{1}{A_0} \sum_{i=1}^{n}A_iB_{n-i} \]

暴力计算每一项即可。

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)}\),那么有:

\[B'(x)A(x)=A'(x) \]

\[(n+1)A_{n+1}=\sum_{i=0}^n(i+1)B_{i+1}A_{n-i} \]

\[(n+1)B_{n+1}A_0=(n+1)A_{n+1}-\sum_{i=0}^{n-1}(i+1)B_{i+1}A_{n-i} \]

\[B_n=\frac{nA_n-\sum_{i=1}^{n-1}iB_iA_{n-i}}{nA_0} \]

一般取 ln 时默认 \(A_0=1\),因此上式也可以写作:

\[B_n=A_n-\frac{1}{n}\sum_{i=1}^{n-1}iB_iA_{n-i} \]

暴力计算每一项即可。

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))\),那么有:

\[B'(x)=A'(x)B(x) \]

\[(n+1)B_{n+1}=\sum_{i=0}^{n}(i+1)A_{i+1}B_{n-i} \]

\[B_{n+1}=\frac{1}{n+1}\sum_{i=0}^{n}(i+1)A_{i+1}B_{n-i} \]

\[B_n=\frac{1}{n}\sum_{i=1}^niA_iB_{n-i} \]

暴力计算每一项即可。

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 后可以得到:

\[\hat g(S)=\operatorname{exp}({\hat F_S(y)}) \]

推导如下:

\[F^k(S) = \sum_{T_1 \cup \cdots \cup T_k} f(T_1) \cdots f(T_k) \]

并且要求 \(T_i\) 两两无交。

做 FMT 可得:

\[\hat F^k(S)=(\hat f(S))^k \]

\[\hat g(S)=\sum_{k\ge0}\frac{1}{k!}(\hat f(S))^k=\operatorname{exp}(\hat f(S)) \]

也就是说对固定每个 \(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\) 所有划分的权值之和,一个划分的权值定义为所有划分出来的集合权值之积。

公式表达为:

\[g_S= \sum_{\pi \in \Pi (S)} \prod_{B \in \pi} f_B \]

其中 \(\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

P10221 [省选联考 2024] 重塑时光

P11834 [省选联考 2025] 岁月

P11714 [清华集训 2014] 主旋律

参考资料

桃酱的算法笔记

ZnPdCo 的博客

各种多项式操作的 n^2 递推

国家集训队2015论文集,集合幂级数的性质与应用及其快速算法,吕凯风

apio2025,集合幂级数在子图计数问题上的应用,cxy

友情链接

集合幂级数学习笔记

posted @ 2026-01-16 16:30  Ff472130  阅读(3)  评论(0)    收藏  举报