集合幂级数简记

记号 & 约定

  • \(U\) 为全集,不加说明时 \(U=\{1,2,\cdots,n\}\)\(2^U\)\(U\) 的全体子集。
  • \(S \cap T\) 为交,\(S \cup T\) 为并,\(S \triangle T\) 为对称差。
  • \(f: 2^U \to \mathbb{R}\),定义集合幂级数 \(F(x)=\sum \limits_{S \subseteq U} f_S x^S\),其中 \(x\) 是形式元。
    • 在 OI 中,视 \(f: 2^U \to \mathbb{Z}_p\),其中 \(p\) 是大质数。
  • \(F(x)\)\(x^S\) 项系数记作 \([x^S]F(x)\)\(f_S\)

高维前缀和 / FMT

问题 1 给定 \(F\),求 \(G\) 满足 \(g_S=\sum\limits_{T \subseteq S} f_T\)

将集合视作 \(n\) 维空间中的 \(2^n\) 个点,\(S\) 的第 \(i\) 维坐标为 \([i \in S]\),权值为 \(f_S\)。则 \(g_S\) 是对某个子空间求权值和,因而这个问题被形象地称作高维前缀和。

对于高维前缀和问题,一般采取逐维前缀和的方法。我们以任意顺序枚举第 \(i\) 维,若 \(i \in S\) 则将 \(g_{S \setminus \{i\}}\) 贡献到 \(g_S\)。对 \(n\) 个维度都做完前缀和后 \(G\) 即为所求,复杂度 \(O(n2^n)\)

for (int i = 0; i < n; i++) 
    for (int s = 0; s < (1 << n); s++) 
        if (s >> i & 1) a[s] += a[s ^ (1 << i)];

问题 2 给定 \(F\),求 \(G\) 满足 \(g_S=\sum\limits_{S \subseteq T} f_T\)

这是问题 1 的超集和版本,可以称作高维后缀和。只要改变贡献方向,令 \(g_S\) 贡献到 \(g_{S \setminus \{i\}}\) 即可。

问题 3 给定 \(G\),求 \(F\) 满足 \(g_S=\sum\limits_{T \subseteq S} f_T\)

这是问题 1 的逆变换,可以称作高维前缀差分。贡献时令 \(g_{S}\) 减去 \(g_{S \setminus \{i\}}\) 即可。

并 / 交 / 对称差卷积

集合幂级数的加法是平凡的对位相加:

\[F+G=\sum_{S \subseteq U}(f_S+g_S)x^S \]

对集合幂级数 \(F,G\),设 \(\oplus\) 是对 \(2^U\) 封闭的二元运算,则 \(F\)\(G\)\(\oplus\) 卷积为

\[F \times G=\sum_{W \subseteq U} \left(\sum_{S \oplus T=W} f_S g_T\right) x^W \]

\(\oplus\) 为并 / 交 / 对称差时,分别得到并卷积 / 交卷积 / 对称差卷积。由于常用二进制状压来表示集合,因此也可以称作 OR 卷积 / AND 卷积 / XOR 卷积。

处理这三类卷积的原则是,构造一个变换 \(\text{T}\),使得 \(\text{T}(F) \cdot \text{T} (G)=\text{T}(F \times G)\),其中 \(\cdot\) 表示点积,即系数的对位乘法。变换 \(\text{T}\) 的本质是用一组权重对系数进行线性组合。

\(\phi(S,T)\)\(f_S\)\([x^T] \text{T}(F)\) 的贡献系数,则

\[\begin{aligned} \text{T} (F)&=\sum_{T \subseteq U} \left(\sum _{S \subseteq U} \phi(S,T)f_S\right) x^T\\ \end{aligned} \]

\(H=F \times G\)。我们希望 \(\forall T \subseteq U,[x^T]\text{T}(F) \times [x^T]\text{T} (G)=[x^T]\text{T}(H)\)。代入得

\[\begin{aligned} \left(\sum _{A \subseteq U} \phi(A,T)f_A\right)\left(\sum _{B \subseteq U} \phi(B,T)g_B\right)&=\sum _{C \subseteq U} \phi(C,T)h_C\\ \sum _{A \subseteq U} \sum _{B \subseteq U} \phi(A,T)\phi(B,T) f_A g_B&=\sum _{C \subseteq U} \phi(C,T)\sum_{A \oplus B=C} f_A g_B\\ \sum _{A \subseteq U} \sum _{B \subseteq U} \phi(A,T)\phi(B,T) f_A g_B&=\sum _{A \subseteq U} \sum _{B \subseteq U} \phi(A \oplus B,T) f_A g_B \end{aligned} \]

\(\phi\) 为变换的特征函数。对比系数得 \(\phi\) 应当满足的条件:

  • \(\phi(A,T) \phi(B,T)=\phi(A \oplus B,T)\)

问题 4 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \cup T=W} f_Sg_T\)

这是并卷积 / OR 卷积。给出构造 \(\phi(S,T)=[S \subseteq T]\)

:::::success[证明]

只要证

\[\begin{aligned} \phi(S,W) \phi(T,W)&=\phi(S\cup T, W)\\ [S \subseteq W][T \subseteq W]&=[(S \cup T) \subseteq W]\\ [S \subseteq W][T \subseteq W]&=[S \subseteq W \land T \subseteq W] \end{aligned} \]

显然成立,证毕。

:::::

考察 \(\text{T}\) 的实际意义

\[[x^W]\text{T}(F)=\sum_{S \subseteq U} [S \subseteq W] f_S \]

可以发现 \(\text{T}(F)\) 恰为 \(F\) 的高维前缀和。称变换 \(\text{T}\)\(\text{FMT}\),并卷积做到 \(O(n2^n)\)

void fwt_or(mint *a, int n, int op) {
    for (int i = 0; i < n; i++) 
        for (int s = 0; s < (1 << n); s++) 
            if (s >> i & 1) a[s] += a[s ^ (1 << i)] * op;
}

问题 5 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \cap T=W} f_Sg_T\)

这是交卷积 / AND 卷积。给出构造 \(\phi(S,T)=[T \subseteq S]\)

证明略去。此时 \(\text{T}\) 的实际意义变为高维后缀和,交卷积做到 \(O(n2^n)\)

void fwt_and(mint *a, int n, int op) {
    for (int i = 0; i < n; i++) 
        for (int s = 0; s < (1 << n); s++) 
            if (s >> i & 1) a[s ^ (1 << i)] += a[s] * op;
}

问题 6 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \triangle T=W} f_Sg_T\)

这是对称差卷积 / XOR 卷积。给出构造 \(\phi(S,T)=(-1)^{|S \cap T|}\)

:::::success[证明]

只要证

\[\begin{aligned} \phi(S,W) \phi(T,W)&=\phi(S\Delta T, W)\\ (-1)^{|S \cap W|} (-1)^{|T \cap W|} &= (-1)^{|(S \Delta T) \cap W|}\\ (-1)^{|S \cap W|+|T \cap W|} &= (-1)^{|(S \Delta T) \cap W|}\\ |S \cap W|+|T \cap W| &\equiv |(S \Delta T) \cap W| \pmod 2 \end{aligned} \]

  • 左式:注意到 \(|A|+|B|=|A \cup B|+|A \cap B|\),从而 \(|S \cap W|+|T \cap W|=|(S \cap W) \cup (T \cap W)|+|(S \cap W) \cap (T \cap W)|=|(S \cup T) \cap W|+|(S \cap T) \cap W|\)

  • 右式:因为 \(S \triangle T=(S \cup T) \setminus (S \cap T)\),从而 $ |(S \triangle T) \cap W| =|(S \cup T) \cap W|-|(S \cap T) \cap W|$。

两式作差

\[\left(|(S \cup T) \cap W|+|(S \cap T) \cap W|\right)-\left(|(S \cup T) \cap W|-|(S \cap T) \cap W|\right)=2|(S \cap T) \cap W| \equiv 0 \pmod 2 \]

故两式对 \(2\) 同余,证毕。

:::::

考察 \(\text{T}\) 的实际意义

\[[x^S]\text{T}(F)=\sum_{T \subseteq U} (-1)^{|S\cap T|} f_T \]

称变换 \(\text{T}\)\(\text{FWT}\)。关于 FWT 的计算,我们在下文说明。

线性代数观点

我们发现 \(\text{T}\) 是一个线性变换,考虑使用矩阵描述。对于二进制 FWT,设位矩阵

\[\begin{bmatrix} \phi(0,0) & \phi(0,1) \\ \phi(1,0) & \phi(1,1) \end{bmatrix} \]

\(\text{T}\) 有逆变换等价于位矩阵可逆。下面给出三种卷积对应的位矩阵及其逆矩阵:

卷积 位矩阵 逆矩阵
并卷积 \(\begin{bmatrix}1 & 0 \\1 & 1\end{bmatrix}\) \(\begin{bmatrix}1 & 0 \\-1 & 1\end{bmatrix}\)
交卷积 \(\begin{bmatrix}1 & 1 \\0 & 1\end{bmatrix}\) \(\begin{bmatrix}1 & -1 \\0 & 1\end{bmatrix}\)
对称差卷积 \(\begin{bmatrix}1 & 1 \\1 & -1\end{bmatrix}\) \(\begin{bmatrix}0.5 & 0.5 \\0.5 & -0.5\end{bmatrix}\)

从这个视角,可以自然地得到 FWT 的线性性

\[\text{FWT}(aF+bG)=a \text{FWT}(F)+b\text{FWT}(G) \]

分治乘法

类比 Karatsuba 做多项式卷积,我们尝试用分治处理集合幂级数的操作。

问题 1 给定 \(F\),求 \(G\) 满足 \(g_S=\sum\limits_{T \subseteq S} f_T\)

钦定一个特殊元素 \(z \in U\),设

\[\begin{aligned} F_0(x)&=\sum _{S \subseteq U, \textcolor{red}{z \notin S}} f_S x^S\\ F_1(x)&=\sum _{S \subseteq U, \textcolor{red}{z \in S}} f_S x^S \end{aligned} \]

将全集改为 \(U \setminus \{x\}\),对 \(F_0,F_1\) 递归求得其 FMT 结果 \(G_0,G_1\)。对 \(g_S\) 进行讨论:

  • \(z \notin S\),则 \(g_S=[x^S] G_0\)
  • \(z \in S\),则 \(g_S=[x^{S \setminus \{z\}}] G_0+[x^{S \setminus \{z\}}]G_1\)

如果将子问题写作 \(\text{FMT}(F_0,F_1)\),则递归过程视作

\[\text{FMT}(F_0,F_1)=(\text{FMT}(F_0),\text{FMT}(F_0)+\text{FMT}(F_1)) \]

\(m=2^n\),由递归式 \(T(m)=2T\left(\dfrac{m}{2}\right)+O(m)\)\(T(m)=O(m \log m)\),从而复杂度 \(O(n2^n)\)

至此,我们用分治解决了 FMT。从几何视角来看,每轮递归相当于一次降维,将空间划分为两个半空间解决问题。

问题 4 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \cup T=W} f_Sg_T\)

钦定一个特殊元素 \(z \in U\),根据 \(z \in S\) 是否成立,将 \(F,G\) 划分为 \(F_0,F_1,G_0,G_1\)。则

\[(F_0,F_1) \times (G_0,G_1)=(F_0 \times G_0, F_0 \times G_1 + F_1 \times G_0+F_1 \times G_1) \]

直接递归需要四次卷积,由 \(T(m)=4T\left(\dfrac{m}{2}\right)+O(m)\)\(T(m)=O(m^2)\),这不好。

注意到

\[(F_0+F_1) \times (G_0+G_1)-F_0 \times G_0=F_0 \times G_1 + F_1 \times G_0+F_1 \times G_1 \]

转而计算 \(F_0 \times G_0\)\((F_0+F_1) \times (G_0+G_1)\),两次卷积,复杂度 \(O(n2^n)\)

问题 1 和问题 4 的本质是对二元组 \((a,b)\) 施加变换 \((a,b) \to (a,a+b)\)。由于 \(a\) 是不含 \(x\) 的结果,\(b\) 是含 \(x\) 的结果,上述变换等价于高维前缀和。这也从另一个角度说明了 FMT 的意义。

问题 6 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \triangle T=W} f_Sg_T\)

钦定一个特殊元素 \(z \in U\),根据 \(z \in S\) 是否成立,将 \(F,G\) 划分为 \(F_0,F_1,G_0,G_1\)。则

\[(F_0,F_1) \times (G_0,G_1)=(F_0 \times G_0+F_1 \times G_1, F_0 \times G_1 + F_1 \times G_0) \]

对二元组 \((a,b)\) 施加变换 \((a,b) \to (a+b,a-b)\)。记

\[\begin{aligned} X&=(F_0+F_1) \times (G_0+G_1)\\ &=F_0 \times G_0+F_1 \times G_1+ F_0 \times G_1 + F_1 \times G_0\\ Y&=(F_0-F_1)\times(G_0-G_1)\\ &=F_0 \times G_0+F_1 \times G_1- F_0 \times G_1 - F_1 \times G_0 \end{aligned} \]

解方程组得

\[\begin{aligned} F_0 \times G_0+F_1 \times G_1&= \dfrac{X+Y}{2}\\ F_0 \times G_1 - F_1 \times G_0&= \dfrac{X-Y}{2}\\ \end{aligned} \]

计算 \(X,Y\) 只要两次卷积。复杂度 \(O(n2^n)\)

考虑将递归转为迭代,从而得到 FWT 实现对称差卷积的做法。

  • FWT 正变换为 \((a,b) \to (a+b,a-b)\)
  • FWT 逆变换为 \((a,b) \to \left(\dfrac{a+b}{2},\dfrac{a-b}{2}\right)\)

只要写出 $(a,b) \to (a+b,a-b) $ 的代码,逆变换在最后除以 \(2^n\) 即可。

void fwt_xor(mint *a, int n, int op) {
    for (int p = 1; p < (1 << n); p <<= 1) 
        for (int i = 0; i < (1 << n); i += (p << 1)) 
            for (int j = 0; j < p; j++) {
                mint x = a[i + j], y = a[i + j + p];
                a[i + j] = x + y, a[i + j + p] = x - y;
            }
    if (op == 1) return;
    mint inv = ~mint(1 << n);
    for (int s = 0; s < (1 << n); s++) a[s] *= inv;
}

\(k\) 进制 FWT

考察 OR / AND / XOR 卷积的本质,将二进制数推广到 \(k\) 进制:

  • OR 推广到对位取 \(\max\)
  • AND 推广到对位取 \(\min\)
  • XOR 推广到不进位加法,即每一位加起来对 \(k\) 取模。

我们仍考虑构造特征函数 \(\phi\) 使得 \(\phi(i,x) \phi(j,x) =\phi(i \oplus j, x)\)

问题 7 给定长为 \(k^n\) 的数组 \(a_0 \sim a_{k^n-1},b_{0} \sim b_{k^n-1}\),求数组 \(c_i\) 满足 \(c_x=\sum \limits_{x = i \oplus j} a_ib_j\)\(\oplus\)\(k\) 进制下对位取 \(\max\)

构造 \(\phi(i,j)=[i \ge j]\),逆变换为

\[\phi^{-1}(i,j)=\begin{cases} 1 \quad& i = j\\ -1 \quad& i = j + 1\\ 0 \quad& i\ne j \land i\ne j+1 \end{cases} \]

:::::success[证明]

只要证

\[\begin{aligned} \phi(i,x) \phi(j,x) &=\phi(i \oplus j, x)\\ [i \ge x][j \ge x] &=[(i \oplus j) \ge x] \end{aligned} \]

\(i \oplus j\) 拆位易证 \((i \oplus j) \ge x\) 的充要条件是 \(i \ge x\land j \ge x\),证毕。

:::::

问题 8 给定长为 \(k^n\) 的数组 \(a_0 \sim a_{k^n-1},b_{0} \sim b_{k^n-1}\),求数组 \(c_i\) 满足 \(c_x=\sum \limits_{x = i \oplus j} a_ib_j\)\(\oplus\)\(k\) 进制下对位取 \(\min\)

构造 \(\phi(i,j)=[i\le j]\) 即可。逆变换和证明同理,不再叙述。

问题 9 给定长为 \(k^n\) 的数组 \(a_0 \sim a_{k^n-1},b_{0} \sim b_{k^n-1}\),求数组 \(c_i\) 满足 \(c_x=\sum \limits_{x = i \oplus j} a_ib_j\)\(\oplus\)\(k\) 进制下不进位加法。

构造 \(\phi(i,j)=\omega^{ij}\),逆变换 \(\phi^{-1}(i,j)=\dfrac{\omega^{ij}}{k}\),其中 \(\omega\)\(k\) 次单位根。

:::::success[证明]

只要证

\[\begin{aligned} \phi(i,x) \phi(j,x) &=\phi(i \oplus j, x)\\ \omega^{ix} \omega^{jx} &= \omega^{(i \oplus j)x}\\ \omega^{(i+j)x} &= \omega^{(i \oplus j)x}\\ (i+j)x &\equiv (i \oplus j) x \pmod k \end{aligned} \]

将条件加强为 \(i+j \equiv i \oplus j \pmod k\)

写出 \(i,j\)\(k\) 进制表示

\[\begin{aligned} i&=\sum_{n \ge 0} a_n k^n=a_0+a_1k+a_2k^2+\cdots\\ j&=\sum_{n \ge 0} b_n k^n=b_0+b_1k+b_2k^2+\cdots \end{aligned} \]

对普通加法 \(i+j\)

\[\begin{aligned} i+j&= \sum_{n \ge 0} (a_n+b_n) k^n=(a_0+b_0)+(a_1+b_1)k+\cdots\\ i+j &\equiv a_0+b_0 \pmod k \end{aligned} \]

对不进位加法 \(i \oplus j\)

\[\begin{aligned} i \oplus j &= \sum_{n \ge 0} ((a_n + b_n) \bmod k )k^n\\ i \oplus j &\equiv (a_0+b_0) \bmod k \pmod k\\ i \oplus j &\equiv a_0+b_0 \pmod k\\ \end{aligned} \]

二者均对 \(a_0+b_0\) 同余,证毕。

:::::

子集卷积

问题 10 给定 \(F,G\),求 \(H\) 满足 \(h_W=\sum \limits_{S \cup T=W \land S \cap T \ne \varnothing} f_Sg_T\)

这个问题被称作子集卷积。子集卷积的组合意义是,将 \(W\) 不重不漏地划分为两个子集 \((S,T)\),一个划分的权值为 \(f_S g_T\),并将权值求和。

\(S \cup T = W\)\(S \cap T \ne \varnothing\) 的条件不好刻画,将其等价为 \(S \cup T=W\)\(|S|+|T|=|W|\)。引入形式元 \(y\) 并用其指数表示 \(|S|\),则

\[\begin{aligned} [x^W y^{|W|}] H&=\sum_{S \cup T=W} [x^S y^{|S|}] F \times [x^{T} y^{|T|}] G\\ [y^{|W|}] H&=\sum _{|S|+|T| = |W|}[y^{|S|+|T|}] FG \end{aligned} \]

关于 \(x\) 的乘法是并卷积,关于 \(y\) 的乘法是多项式卷积。由于并卷积需要做 \(n\) 次,复杂度 \(O(n^2 2^n)\) 是瓶颈,\(y\) 维度可以暴力卷积。

int n;
mint f[21][N], g[21][N], h[21][N];

void _main() {
    cin >> n;
    for (int s = 0; s < (1 << n); s++) cin >> f[popcount(s)][s];
    for (int s = 0; s < (1 << n); s++) cin >> g[popcount(s)][s];
    for (int i = 0; i <= n; i++) fwt_or(f[i], n, 1), fwt_or(g[i], n, 1);
    for (int k = 0; k <= n; k++) 
        for (int i = 0; i <= k; i++) 
            for (int s = 0; s < (1 << n); s++) h[k][s] += f[i][s] * g[k - i][s];
    for (int i = 0; i <= n; i++) fwt_or(h[i], n, -1);
    for (int s = 0; s < (1 << n); s++) cout << h[popcount(s)][s] << ' ';
}

半在线卷积

问题 11 给定 \(G,H,f_{\varnothing}\),求 \(F\) 满足 \(f_W=h_W\sum \limits_{S \cup T=W \land S \cap T \ne \varnothing} f_S g_T\)。保证 \(g_{\varnothing}=0,h_{\varnothing}=1\)

问题形如一个子集卷积,但卷积式包含 \(f\),这种卷积被称作半在线子集卷积,简称半在线卷积。

考察 \(S \cup T = W\)\(|S|+|T|=|W|\) 的条件。由 \(g_{\varnothing}=0\) 可得 \(|T| \ge 1\),进而 \(|S|<|W|\)。这表明,\(f_W\) 的值只依赖 \(|S|<|W|\)\(f_S\)。我们以 \(|W|\) 为枚举阶段,逐层计算 \(F\) 即可。

具体地,我们逐层枚举 \(k=1,2,\cdots,n\),在第 \(k\) 层对 \(y\) 卷积得到前缀贡献 \(R=\sum \limits_{i=0}^{k-1} \text{FMT}([y^i]F) \cdot \text{FMT}([y^{k-i}]G)\),对 \(R\) 做一个 FMT 逆变换后得到真实的 \(R\),对 \(|W|=k\) 的项乘上 \(h_W\),再将 \(R\) 做 FMT 传递给下一层。复杂度 \(O(n^2 2^n)\)

int n;
mint f[21][N], g[21][N], h[N];

void _main() {
    cin >> n >> f[0][0];
    for (int i = 0; i < (1 << n); i++) cin >> g[popcount(i)][i];
    for (int i = 0; i < (1 << n); i++) cin >> h[i];
    for (int i = 1; i <= n; i++) fwt_or(g[i], n, 1);
    fwt_or(f[0], n, 1);
    for (int k = 1; k <= n; k++) {
        static mint r[N];
        fill(r, r + (1 << n), 0);
        for (int i = 0; i < k; i++)
            for (int s = 0; s < (1 << n); s++)
                r[s] += f[i][s] * g[k - i][s];
        fwt_or(r, n, -1);
        for (int s = 0; s < (1 << n); s++)
            if (popcount(s) == k) f[k][s] = r[s] * h[s];
        fwt_or(f[k], n, 1);
    }
    for (int s = 0; s < (1 << n); s++) cout << f[popcount(s)][s] << ' ';
} 

初等函数

求逆

问题 12 给定 \(G\),求 \(F\) 使得 \(F \times G=1\)\(\times\) 为子集卷积。保证 \(g_{\varnothing} \ne 0\)

\(F \times G=1\) 代入子集卷积定义式

\[\begin{aligned} \sum_{T \subseteq S} f_T g_{S \setminus T}&=[S = \varnothing] \end{aligned} \]

\(S \ne \varnothing\)

\[\begin{aligned} \sum_{T \subset S} f_T g_{S \setminus T}&=-f _S g_{\varnothing}\\ f_S&=-\dfrac{1}{g_{\varnothing}}\sum_{T \subset S} f_T g_{S \setminus T} \end{aligned} \]

这是一个 \(h_S=-\dfrac{1}{g_{\varnothing}}\) 的半在线子集卷积,复杂度 \(O(n^2 2^n)\)

int n;
mint f[21][N], g[21][N];

void _main() {
    cin >> n;
    for (int i = 0; i < (1 << n); i++) cin >> g[popcount(i)][i];
    const mint h = mint(-1) / g[0][0];
    for (int i = 1; i <= n; i++) fwt_or(g[i], n, 1);
    f[0][0] = -h, fwt_or(f[0], n, 1);
    for (int k = 1; k <= n; k++) {
        static mint r[N];
        fill(r, r + (1 << n), 0);
        for (int i = 0; i < k; i++)
            for (int s = 0; s < (1 << n); s++)
                r[s] += f[i][s] * g[k - i][s];
        fwt_or(r, n, -1);
        for (int s = 0; s < (1 << n); s++)
            if (popcount(s) == k) f[k][s] = r[s] * h;
        fwt_or(f[k], n, 1);
    }
    for (int s = 0; s < (1 << n); s++) cout << f[popcount(s)][s] << ' ';
} 

\(\exp\)

问题 13 给定 \(F\),求 \(\exp F=\sum \limits_{n \ge 0} \dfrac{F^n(x)}{n!}\)\(\times\) 为子集卷积。保证 \(f_{\varnothing}=0\)

考察 \(\exp F\) 的组合意义:将 \(S\) 不重不漏地划分为若干无标号子集,每种划分方式的权值为子集权值乘积,\([x^S] \exp F\) 即为所有权值求和的结果。

上式具有这个组合意义的原因是,定义式相当于枚举划分的段数 \(n\),除以 \(n!\) 是消序。保证 \(f_{\varnothing}=0\) 表明我们不会划出空段,从而求和收敛,\(\exp F\) 有定义。

对着组合意义 DP:

  • 钦定一个特殊元素 \(z \in S\)。对于 \(S\) 的一组划分方案,必然有且仅有一个子集 \(T' \subseteq S \setminus \{z\}\) 包含 \(x\),设 \(T'=T \cup \{z\}\)

  • 我们枚举 \(T\) 进行转移,将贡献拆成两部分:

    • \(T\) 内部进行划分的方案数为 \(f_{T \cup \{z\}}\)
    • 对 $S $ 内部进行划分的方案数为 \([x^S] \exp F\)

    转移式

    \[[x^S] \exp F=\sum_{T \subseteq S \setminus \{z\}} f_{T \cup \{z\}} [x^{S \setminus T \setminus \{z\}}] \exp F \]

可以做到 \(O(3^n)\),并不优秀。

进一步,考虑钦定 \(z=\max S\),此时 \(S \setminus \{z\}, T \in \{1,2,\cdots,z-1\}\)。问题转化为半在线子集卷积,复杂度 \(O(n^2 2^n)\)

另一种引入形式元 \(y\) 的做法需要使用逆元,故泛用性不如组合意义做法。

int n;
u64 f[N], g[N], a[21][N], b[21][N];

void _main() {
    cin >> n;
    for (int i = 0; i < (1 << n); i++) cin >> f[i];
    g[0] = 1;
    for (int z = 0; z < n; z++) {
        const int u = 1 << z;
        for (int i = 0; i <= z; i++) fill(a[i], a[i] + u, 0), fill(b[i], b[i] + u, 0);
        for (int s = 0; s < u; s++) a[popcount(s)][s] = f[s | u], b[popcount(s)][s] = g[s];
        for (int i = 0; i <= z; i++) fwt_or(a[i], z, 1), fwt_or(b[i], z, 1);
        for (int i = 0; i <= z; i++) {
            static u64 r[N]; fill(r, r + u, 0);
            for (int j = 0; j <= i; j++)
                for (int s = 0; s < u; s++)
                    r[s] += a[j][s] * b[i - j][s];
            fwt_or(r, z, -1);
            for (int s = 0; s < u; s++)
                if (popcount(s) == i) g[s | u] = r[s];
        }
    }
    for (int i = 0; i < (1 << n); i++) cout << g[i] << ' ';
} 

\(\ln\)

问题 14 给定 \(F\),求 \(\ln F\),定义 \(\ln\) 为集合幂级数 \(\exp\) 的逆运算。保证 \(f_{\varnothing}=1\)

\(\exp\) 的转移进行变形

\[\begin{aligned} f_S&=\sum_{T \subseteq S \setminus \{z\}} f_{S \setminus T \setminus \{z\}}[x^{T \cup \{z\}}] \ln F\\ f_S&=f_{\varnothing}[x^S] \ln F+\sum_{T \subset S \setminus \{z\}} f_{S \setminus T \setminus \{z\}}[x^{T \cup \{z\}}] \ln F\\ [x^S] \ln F&=f_S-\sum_{T \subset S \setminus \{z\}} f_{S \setminus T \setminus \{z\}}[x^{T \cup \{z\}}] \ln F \end{aligned} \]

同理钦定 \(z =\max S\) 转化为半在线子集卷积,复杂度 \(O(n2^n)\)

int n;
u64 f[N], g[N], a[21][N], b[21][N];

void _main() {
    cin >> n;
    for (int i = 0; i < (1 << n); i++) cin >> f[i];
    g[0] = 0;
    for (int z = 0; z < n; z++) {
        const int u = 1 << z;
        for (int i = 0; i <= z; i++) fill(a[i], a[i] + u, 0), fill(b[i], b[i] + u, 0);
        for (int s = 0; s < u; s++) b[popcount(s)][s] = f[s];
        for (int i = 0; i <= z; i++) fwt_or(b[i], z, 1);
        for (int i = 0; i <= z; i++) {
            static u64 r[N]; fill(r, r + u, 0);
            for (int j = 0; j <= i; j++)
                for (int s = 0; s < u; s++)
                    r[s] += a[j][s] * b[i - j][s];
            fwt_or(r, z, -1);
            for (int s = 0; s < u; s++)
                if (popcount(s) == i) 
                    g[s | u] = f[s | u] - r[s], a[i][s] = g[s | u];
            fwt_or(a[i], z, 1);
        }
    }
    for (int i = 0; i < (1 << n); i++) cout << g[i] << ' ';
}

练习

CF383E Vowels

给定 \(n\) 个集合 \(T_i\)\(T_i\)\(\textcolor{red}{\tt{a \sim x}}\) 的子集。对字符集的所有子集 \(S\) 求出 \(\sum \limits_{i=1}^n [S \cap T_i \ne \varnothing]\)

\(n \le 10^4\),4 秒,256 MB。

:::::success[题解]

\(f_S=\sum \limits_{i=1}^n [S=T_i]\),题意即为求 \(G\) 满足

\[g_S=\sum_{S \cap T \ne \varnothing} f_T \]

\(S \cap T \ne \varnothing\) 做减法原理

\[\begin{aligned} g_S&=\sum_{S \cap T \ne \varnothing} f_T\\ &=\sum_{S \subseteq U}f_S -\sum_{S \cap T=\varnothing}f_T\\ &=n-\sum_{T \subseteq (U \setminus S)} f_T \end{aligned} \]

\(F\) 做一次 FMT 即可,复杂度 \(O(n+|\Sigma|2^{|\Sigma|})\)

:::::

:::::info[代码]

const int N = 2e7 + 5;
i64 n, f[N];

void _main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        string t; cin >> t;
        int s = 0;
        for (char c : t) s |= 1 << (c - 'a');
        f[s]++;
    }
    fwt_or(f, 24, 1);
    i64 res = 0;
    const int u = (1 << 24) - 1;
    for (int s = 0; s <= u; s++) {
        i64 gs = n - f[u ^ s];
        res ^= gs * gs;
    } cout << res;
} 

:::::

ARC100E Or Plus Max

给定长为 \(2^n\) 的序列 \(a_0 \sim a_{2^n-1}\)。对所有 \(k=1,2,\cdots,2^{n}-1\)\(\max \limits_{i \ne j \land (i \text{ or } j) \le k}(a_i+a_j)\)

\(n \le 18\),2 秒,1024 MB。

:::::success[题解]

考虑将条件变为 \(i \text{ or } j =k\),算出答案以后做一个前缀 \(\max\) 即可。

直接做是 \((\max,+)\) 的 OR 卷积,但无法处理 \(i \ne j\) 的限制。

转而计算 \(a_i\) 的最大值和次大值,取二者之和作为答案,从而保证了 \(i \ne j\) 的限制。对 \(a_i\) 做一次 FMT 即可更新答案,不必再做逆变换。

复杂度 \(O(n2^n)\)

:::::

:::::info[代码]

const int N = (1 << 18) + 5;
using node = pair<int, int>;
int n, a[N];
node f[N];
node operator+ (const node& a, const node& b) {
	vector<int> vec = {a.first, a.second, b.first, b.second};
	sort(vec.begin(), vec.end(), greater<int>());
	return {vec[0], vec[1]};
}
void _main() {
	cin >> n;
	for (int i = 0; i < (1 << n); i++) cin >> a[i], f[i] = {a[i], 0};
	for (int i = 0; i < n; i++) {
		for (int s = 0; s < (1 << n); s++) {
			if (s >> i & 1) f[s] = f[s] + f[s ^ (1 << i)];
	int res = 0;
	for (int s = 1; s < (1 << n); s++) {
		chkmax(res, f[s].first + f[s].second);
		cout << res << '\n';
	}
} 

:::::

ABC288G 3^N Minesweeper

给定长为 \(3^n\) 的序列 \(a_i\)

对于位置 \(i \in [0,3^n)\),位置 \(i\) 上有 \(b_i\) 个物品,且 \(b_i \in \{0,1\}\)。给定 \(a_i=\sum \limits_{i \ominus j } b_j\),求 \(b_0 \sim b_{3^n-1}\)。其中,\(\ominus\) 表示将 \(i,j\) 写成三进制后,逐位数码之差 \(\le 1\)

\(n \le 12\),2 秒,1024 MB。

:::::success[题解]

容易得到 \(i \ominus j\) 的真值表,进而变换 \(b \to a\) 的位矩阵为

\[A=\begin{bmatrix} 1 & 1 & 0\\ 1 & 1 & 1\\ 0 & 1 & 1 \end{bmatrix} \]

对位矩阵求逆得到

\[A^{-1}=\begin{bmatrix} 0 & 1 & -1\\ 1 & -1 & 1\\ -1 & 1 & 0 \end{bmatrix} \]

做一次 FWT 状物即可。复杂度 \(O(n3^n)\)

:::::

:::::info[代码]

const int N = 6e5 + 5;
int n, a[N];

void _main() {
    cin >> n;
    const int u = pow(3, n) - 1;
    for (int i = 0; i <= u; i++) cin >> a[i];
    for (int p = 1; p <= u; p *= 3)
        for (int i = 0; i <= u; i += 3*p) 
            for (int j = 0; j < p; j++) {
                int x = a[i + j], y = a[i + j + p], z = a[i + j + 2*p];
                a[i + j] = y - z, a[i + j + p] = x + z - y, a[i + j + 2*p] = y - x;
            }
    for (int i = 0; i <= u; i++) cout << a[i] << ' ';
} 

:::::

CF662C Binary Table

给定一个 \(n \times m\) 的 01 矩阵 \(a_{i,j}\), 一次操作选择一行或一列,将其 flip。最小化 1 的数目并输出。

\(n \le 20,m \le 10^5\),6 秒,250 MB。

:::::success[题解]

\(T_j=\{i \mid a_{i,j}=1\}\),枚举 \(S\) 为进行操作的行号集合,答案为

\[\min_S \sum_{j=1}^m \min(|S \triangle T_j|, n-|S \triangle T_j|) \]

复杂度 \(O(m2^n)\) 无法通过。采用 01 转化 trick,设

\[\begin{aligned} f_S&=[\exists j \in [1,m], T_j=S]\\ g_S&=\min(|S|,n-|S|) \end{aligned} \]

代入答案式

\[\min_S \sum_{T} f_T g_{S \triangle T} \]

\(F,G\) 做对称差卷积后,取全局 \(\min\) 为答案。复杂度 \(O(nm+n2^n)\)

:::::

:::::info[代码]

const int N = 1.1e6 + 5, M = 1e5 + 5;
int n, m;
mint f[N], g[N];
char s[21][M];

void _main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> (s[i] + 1);
    for (int j = 1; j <= m; j++) {
        int v = 0;
        for (int i = 1; i <= n; i++) 
            if (s[i][j] == '1') v |= 1 << (i - 1);
        f[v]++;
    }
    for (int s = 0; s < (1 << n); s++) g[s] = min(popcount(s), n - popcount(s));
    fwt_xor(f, n, 1), fwt_xor(g, n, 1);
    for (int s = 0; s < (1 << n); s++) f[s] *= g[s];
    fwt_xor(f, n, -1);
    cout << *min_element(f, f + (1 << n));
}

:::::

posted @ 2026-08-01 17:31  stripe_python  阅读(0)  评论(0)    收藏  举报