集合幂级数
定义
所谓集合幂级数,就是以集合作为幂的级数。
设 \(U=\{1,2,3,\cdots,n\}\) 为全集,则集合幂级数为 \(f(x)=\sum_{S\subseteq U}f_Sx^S\)。
如果把集合 \(S\) 中每一个数有没有压成一个二进制数,那么也可以看成是 \(n\) 元 \(\bmod\space x_i^2(i=1,2,3,\cdots n)\) 的多元幂级数。
卷积
要定义两个集合幂级数的卷积,首先要定义 \(x^S\times x^T\) 的结果是什么。
根据实际应用,大致可以分为以下几类:
- \(x^S\times x^T=x^{S\cup T}\):集合并卷积,也被称为 or 卷积。
- \(x^S\times x^T=x^{S\cap T}\):集合交卷积,也被称为 and 卷积。
- \(x^S\times x^T=x^{S\oplus T}\):集合异或卷积,也被称为 xor 卷积。
以上三种卷积我们都可以做到 \(O(n2^n)\) 计算。
还有一类较为特殊:
这种卷积被称为子集卷积,可以在 \(O(n^22^n)\) 的时间内计算。
然而使用上述集合幂级数的描述还是太抽象了,事实上可以把一个集合看成一个 \(n\) 位的二进制数,一个形式幂级数就是长为 \(2^n\) 的一个序列。集合并就是按位或,集合交就是按位与,集合异或就是按位异或。
FMT 快速莫比乌斯变换
FMT 用来快速计算集合并卷积和集合交卷积。
以集合并卷积为例,已知两个数列 \(a,b\),要求解 \(c\) 满足 \(c_S=\sum_{T\cup U=S}a_Tb_U\)。
暴力枚举两个序列中的两项,复杂度 \(O(4^n)\),无法接受。
回顾多项式卷积的过程,是先用 FFT 变换出这个多项式的点值序列,然后用点值相乘,再逆变换回来。
仿照 FFT,设序列 \(a\) 的 FMT 变换 \(A\) 为 \(A_S=\sum_{T\subseteq S}a_T\),则有:
所以只需要求出 \(a,b\) 的 FMT 变换然后点值相乘求出 \(C\) 再逆变换回去就好了。
现在要做 FMT 变换,直接枚举子集可以做到 \(O(3^n)\),但是还是不够快。
考虑位运算每一位的独立性,按位做贡献。从小到大考虑每一位,假设当前位为 \(x\),若 \(x\notin S\),则将 \(a_{S\cup\{x\}}\) 加上 \(a_S\)。
其实就是高维前缀和。这样做复杂度是 \(O(n2^n)\) 的,效率很高。
逆变换直接把加换成减,也就是高维差分,同样是 \(O(n2^n)\) 的。
集合交卷积把高维前缀和换成高维后缀和就行。
void FMTor(int *f,int n,int mode){
for(int w=1;w<n;w<<=1)
for(int i=0;i<n;i+=(w<<1))
for(int j=0;j<w;j++)
f[i+w+j]=(f[i+w+j]+mode*f[i+j]+mod)%mod;
}
void FMTand(int *f,int n,int mode){
for(int w=1;w<n;w<<=1)
for(int i=0;i<n;i+=(w<<1))
for(int j=0;j<w;j++)
f[i+j]=(f[i+j]+mode*f[i+w+j]+mod)%mod;
}
FWT 快速沃尔什变换
FWT 用来快速计算集合异或卷积。
但是想要用人类智慧直接构造出 FWT 变换序列还是太困难了,考虑仿照 FMT 变换。
如果把一个序列 \(a\) 看做一个列向量,那么 FMT 变换可以看做一个矩阵 \(T\) 乘上 \(a\) 得到了 \(A\),对于 FWT 也就是:
于是可以得到这个矩阵 \(T\) 必须满足 \(T_{i,j}T_{i,k}=T_{i,j\oplus k}\),因为还要计算逆变换,其还要可逆。
手动构造这个矩阵还是太累了,考虑位运算对于每一位的独立性。假如能手动构造出 \(2\times 2\) 的矩阵 \(T_0\) 满足上述条件,那么只要令 \(T_{i,j}=\prod_{x=1}^nT_{0,[x\in i],[x\in j]}\) 即可,证明:
经过手玩可以发现,\(T_0=\begin{bmatrix}1&1\\1&-1\end{bmatrix}\),\(T^{-1}=\begin{bmatrix}\frac{1}{2}&\frac{1}{2}\\\frac{1}{2}&-\frac{1}{2}\end{bmatrix}\)。
依然考虑位运算对于每一位的独立性,从小到大考虑每一位,假设当前位为 \(x\),若 \(x\notin S\),设 \(T=S\cup\{x\}\),则上面那个矩阵可以直接套过来他们两个的贡献:\(\begin{bmatrix}a_S\to a_S=1&a_S\to a_T=1\\a_T\to a_S=1&a_T\to a_T=-1\end{bmatrix}\)。
void FWT(int *f,int n,int mode){
for(int w=1;w<n;w<<=1)
for(int i=0;i<n;i+=(w<<1))
for(int j=0;j<w;j++){
int x=f[i+j],y=f[i+w+j];
f[i+j]=(x+y)%mod,f[i+w+j]=(x-y+mod)%mod;
if(mode==-1)f[i+j]=f[i+j]*inv2%mod,f[i+w+j]=f[i+w+j]*inv2%mod;
}
}
线性性
通过上面对 FWT 的矩阵乘法形式理解,可以发现 FWT 是线性变换,套用这个东西可以得到 FMT 也是线性变换。
也就是说 \(\operatorname{FWT}(a)+\operatorname{FWT}(b)=\operatorname{FWT}(a+b)\),\(c\operatorname{FWT}(a)=\operatorname{FWT}(ca)\)。
例题
CF449D
容斥一下。and 为 \(0\) 这个条件相当于每一位至少有一个为 \(0\),设 \(f_S\) 表示 and 必须为 \(S\) 的方案数,\(g_S\) 表示 and 是 \(S\) 的超集的方案数,答案为 \(f_0\)。可以得到关系式 \(g_S=\sum_{S\subseteq T}f_T\),\(f_0\) 可以通过子集反演由 \(g\) 求出。
考虑 \(g_S\) 怎么求。发现可以先用高维后缀和求出 \(S\) 超集有多少个数,然后在这些数里随便选就能构成超集,也就是 \(2^x\) 的贡献。
另外一种做法是集合幂级数。转化题目为求出:
其中 \(x^S\times x^T=x^{S\cap T}\),\(U\) 为全集。
注意到其中每一个多项式只有两项,不妨手玩一下观察规律,发现 $\operatorname{FMT}(x{a_i}+xU) 中每一位只可能是 \(1\) 或者 \(2\)。
考虑到 FMT 的线性性,不妨先算出 \(f=\sum_{i=1}^n\operatorname{FMT}(x^{a_i}+x^U)=\operatorname{FMT}(\sum_{i=1}^n(x^{a_i}+x^U))\)。然后依次考虑每一位,它一定是由若干个 \(1\) 和若干个 \(2\) 加起来的,不妨设 \(x\) 个 \(1\),\(y\) 个 \(2\),于是可以得到方程组:
可以解出:
这样就可以求出每一位上有多少个多项式为 \(1\) 和 \(2\),然后直接点乘起来再逆 FMT 回去即可。
CF662C
显然应该枚举翻转哪些行,假设翻转的行的集合为 \(S\),对每一列考虑,设第 \(i\) 列的数原来是 \(a_i\),如果翻转这一列能够让 \(1\) 变少,那么就翻转。设翻转行集合为 \(S\) 时的答案为 \(ans_S\),也就是说:
答案即为 \(\min\{ans_S\}\)。
这个形式看不出来啥东西,换一种表示。设 \(f_S\) 表示列值为 \(S\) 的有多少个,则有:
设 \(g_S=\min\{\operatorname{popcount}(S),n-\operatorname{popcount}(S)\}\),则:
什么这不是我们 FWT 吗。
UOJ310
首先注意到如果能取出一个集合 \(S\) 其异或和为 \(0\),这个集合所有 \(2^{|S|}\) 种划分方案都符合条件。所以题目转化为一个异或和为 \(0\) 的集合 \(S\) 的贡献为 \(2^{|S|}\),求出所有的贡献。使用集合幂级数表示为:
注意到每个多项式也是只有两项,手玩可以发现里面只可能有 \(-1\) 或者 \(3\)。所以仿照之前的做法解方程就做完了。
子集卷积
子集卷积相比普通的集合并卷积,增加了交集为空的限制。考虑把限制拆成 \(i\cup j=k\) 和 \(|i|+|j|=|k|\)。
对于每个 \(i=0,1,2,\cdots,n\),构造占位多项式 \(f_i(S)=\begin{cases}a_S,|S|=i\\0,|S|\neq i\end{cases}\)。同样对 \(b\) 序列构造 \(g_i(S)\)。
对所有的 \(f_i(S)\) 和 \(g_i(S)\) 做一遍 FMT 变换变为 \(F_i(S)\) 和 \(G_i(S)\),然后枚举 \(i,j\),令 \(H_{i+j}(S)\) 为 \(F_i(S)\) 和 \(G_j(S)\) 的点值相乘,再将所有 \(H_i(S)\) 逆变换为 \(h_i(S)\),最终的答案序列 \(c\) 即为 \(c_S=h_{|S|}(S)\)。容易发现这样的值能被正确贡献。
时间复杂度 \(O(n^22^n)\)。
如果要做形如 \(dp_S=\sum_{T\subset S}dp_Tg_{S-T}\) 的 DP,可以使用半半在线子集卷积。
同样地对 \(dp_S\) 构造 \(n+1\) 个占位多项式 \(f_i(S)\),按 \(i\) 从小到大的顺序计算即可。复杂度 \(O(2^nn^2)\)。
例题
P3349
设 \(f_{u,i,S}\) 为 \(u\) 子树内的点映射到 \(S\) 中,且 \(u\) 映射为 \(i\) 的方案数。转移相当于子集卷积,只保留 \(|S|=size\) 的位即可。\(O(2^nn^3)\)。
P4221
设 \(v_S\) 表示 \(S\) 是否合法,可以 \(O(n2^n)\) 简单计算,\(w_S\) 为 \(S\) 内点权和,\(f_S\) 为 \(S\) 的答案,则有 DP:
使用半半在线子集卷积即可。

浙公网安备 33010602011771号