集合幂级数简记
记号 & 约定
- 记 \(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\),设 \(\oplus\) 是对 \(2^U\) 封闭的二元运算,则 \(F\) 与 \(G\) 的 \(\oplus\) 卷积为
当 \(\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)\) 的贡献系数,则
记 \(H=F \times G\)。我们希望 \(\forall T \subseteq U,[x^T]\text{T}(F) \times [x^T]\text{T} (G)=[x^T]\text{T}(H)\)。代入得
称 \(\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[证明]
只要证
显然成立,证毕。
:::::
考察 \(\text{T}\) 的实际意义
可以发现 \(\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[证明]
只要证
-
左式:注意到 \(|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|$。
两式作差
故两式对 \(2\) 同余,证毕。
:::::
考察 \(\text{T}\) 的实际意义
称变换 \(\text{T}\) 为 \(\text{FWT}\)。关于 FWT 的计算,我们在下文说明。
线性代数观点
我们发现 \(\text{T}\) 是一个线性变换,考虑使用矩阵描述。对于二进制 FWT,设位矩阵
\(\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 的线性性
分治乘法
类比 Karatsuba 做多项式卷积,我们尝试用分治处理集合幂级数的操作。
问题 1 给定 \(F\),求 \(G\) 满足 \(g_S=\sum\limits_{T \subseteq S} f_T\)。
钦定一个特殊元素 \(z \in U\),设
将全集改为 \(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)\),则递归过程视作
记 \(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\)。则
直接递归需要四次卷积,由 \(T(m)=4T\left(\dfrac{m}{2}\right)+O(m)\) 得 \(T(m)=O(m^2)\),这不好。
注意到
转而计算 \(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\)。则
对二元组 \((a,b)\) 施加变换 \((a,b) \to (a+b,a-b)\)。记
解方程组得
计算 \(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]\),逆变换为
:::::success[证明]
只要证
对 \(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[证明]
只要证
将条件加强为 \(i+j \equiv i \oplus j \pmod k\)。
写出 \(i,j\) 的 \(k\) 进制表示
对普通加法 \(i+j\)
对不进位加法 \(i \oplus j\)
二者均对 \(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|\),则
关于 \(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\) 代入子集卷积定义式
若 \(S \ne \varnothing\) 则
这是一个 \(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\) 的转移进行变形
同理钦定 \(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\) 满足
对 \(S \cap T \ne \varnothing\) 做减法原理
对 \(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\) 的位矩阵为
对位矩阵求逆得到
做一次 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\) 为进行操作的行号集合,答案为
复杂度 \(O(m2^n)\) 无法通过。采用 01 转化 trick,设
代入答案式
对 \(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));
}
:::::

浙公网安备 33010602011771号