集合幂级数板子
我说这其实不难,我做不明白可能是因为我太蠢了。
一些不严谨的定义
我们知道有个东西叫多项式,多项式 \(f(x) = \sum\limits_{i = 0}^{n} a_i x^{i}\)。
先引出一个新的概念:形式幂级数。这个东西与多项式类似,区别在于它的系数数量是无限的,即,形式幂级数 \(F(x) = \sum\limits_{i = 0}^{\infty} a_i x^i\)。同时,为了表示幂级数的每一项,我们定义 \([x^i] F(x) = a_i\)。
关于形式幂级数有啥用?我也不到啊
接下来,仿照形式幂级数,我们定义集合幂级数:\(F(x) = \sum\limits_{i = 0}^{\infty} a_i x^i\)。其与形式幂级数的区别是,形式幂级数的乘法与多项式乘法类似,而集合幂级数的乘法则类似于子集卷积(将第 \(i\) 项视为 \(i\) 的二进制位所表示的集合)。算法竞赛中,我们一般认为集合幂级数存在最高项,即集合幂级数满足 \(\forall i > n, [x^i] F(x) = 0\)。通过引入集合幂级数,我们做到了重定义多项式乘法为自己卷积的事情。
幂级数的平方,加减,\(\text{exp}\) 与 \(\text{ln}\) 运算得到的均为幂级数。
集合幂级数半在线子集卷积
考虑如下问题:给定集合幂级数 \(g\),要求集合幂级数 \(f\),其中 \(f\) 的递推式为 \(f_S = \varphi(S) \sum\limits_{T \in S} f_T g_{S - T}\),其中 \(\varphi(S)\) 是一个可以快速计算的函数。显然该递推过程与子集卷积的区别是,我们要一定程度上在线地做它,故将其称为半在线子集卷积。
做法:考虑仿照子集卷积,引入辅助元。定义 \(F_{i, S} = \sum [|S| = i] f_S\),\(G_{i, S} = \sum [|S| = i] g_S\)。引入辅助幂级数 \(H_{i, S}\),我们会将计算的贡献存入 \(H\) 当中。
考虑从小到大枚举 \(i\),并考虑 \(F_i\) 的贡献。对于当前处理的 \(i\),假定 \(F_i\) 是已经处理好的真实值,先将其 \(\text{fwt}\) 一遍,再对于所有的 \(G_i\),将新的 \(F_i\) 与 \(\text{fwt}(G_i)\) 相乘,计算贡献 \(H_{i + j, s} \leftarrow F_{i, S} G_{j, S}\)(贡献式中的 \(F\) 与 \(G\) 均为 \(fwt\) 后的)。贡献计算结束之后,考虑当前 \(H_{i + 1}\) 的值必定是真实值,将其 \(ifwt\) 一遍并计算真实的 \(F_{i + 1}\) 即可。
关于复杂度,设集合大小不超过 \(n\),则你要枚举一遍大小,每次枚举的内层的时间复杂度是 \(O(2^n n)\) 的,因此总时间复杂度为 \(O(2^n n^2)\)。
P12232 【模板】集合幂级数求逆
半在线子集卷积模板题,将式子化为半在线子集卷积的通用格式并计算即可。
P12230 【模板】集合幂级数 exp / P13843 【模板】集合幂级数 exp(非素数模数)
题意:给定集合幂级数 \(f(x)\),保证 \([x^0]f(x) = 0\),求 \(e^{f(x)}\)。
考虑泰勒展开,我们有 \(e^{f(x)} = \sum\limits_{n \ge 0} \frac{f^n(x)}{n!}\)。
考虑这个式子的组合意义,将 \(x\) 视为集合,即可将式子理解为:有 \(n\) 个元素,每个集合 \(S\) 有权值 \(f_S\),其中空集的权值为 \(0\)。定义一个集合的划分方案的贡献为,划分的所有子集的权值之积。对全集的每个子集,求它的所有划分方案的贡献之和,划分出的集合是无序的,即改变划分出的集合的顺序,并不会形成新的划分方案。
考虑这个咋转移,设 \(F_S\) 表示集合 \(S\) 的答案,因为集合是无序的,因此考虑钦定转移顺序。我们钦定最后枚举的子集是包含当前集合的 \(\text{lowbit}\),则有转移:
考虑这个转移与半在线子集卷积的区别:我们有一个 \(\text{lowbit}(S) \in T\) 的限制,于是考虑魔改对 \(f\) 做的 \(\text{fwt}\),改成只有 \(\text{lowbit}(i + j) = \text{lowbit}(i + j + k)\) 的时候才转移。我们知道 \(\text{fwt}_S(w)\) 表示的是,\(\sum\limits_{T \subseteq S} w_T\),而在魔改之后,新的 \(\text{fwt}_S(w)\) 存的显然就是 \(\sum\limits_{T \subseteq S, \text{lowbit}(T) = \text{lowbit}(S)} w_T\),而这就是我们所需要的 \(\text{fwt}(f)\)。同样可以发现对 \(F\) 的 \(\text{ifwt}\) 也需要改变,就是新的 \(\text{fwt}\) 做逆变换。
这个东西显然是支持任意模数的。
P13843 代码:
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define mid ((l + r) >> 1)
#define lowbit(x) ((x) & -(x))
using namespace std;
constexpr int N = 20, mod = 998244353;
#define Z unsigned long long
int n;
Z f[N + 1][1 << N], g[N + 1][1 << N], h[N + 1][1 << N];
void fwt(Z a[], Z f = 1) {
for (int w = 2, k = 1; w <= (1 << n); w <<= 1, k <<= 1) {
for (int i = 0; i < (1 << n); i += w) {
for (int j = 0; j < k; ++j) {
a[i + j + k] += a[i + j] * f;
}
}
}
}
void fwt2(Z a[], Z f = 1) {
for (int w = 2, k = 1; w <= (1 << n); w <<= 1, k <<= 1) {
for (int i = 0; i < (1 << n); i += w) {
for (int j = 0; j < k; ++j) {
if (lowbit(i + j) == lowbit(i + j + k)) {
a[i + j + k] += a[i + j] * f;
}
}
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < (1 << n); ++i) {
cin >> f[__builtin_popcount(i)][i];
}
g[0][0] = 1;
for (int i = 0; i <= n; ++i) {
fwt2(f[i]);
}
for (int i = 0; i < n; ++i) {
fwt(g[i]);
for (int j = 1; i + j <= n; ++j) {
for (int k = 0; k < (1 << n); ++k) {
h[i + j][k] += f[j][k] * g[i][k];
}
}
fwt(g[i], -1);
fwt2(h[i + 1], -1);
for (int j = 0; j < (1 << n); ++j) {
g[i + 1][j] = h[i + 1][j];
}
}
for (int i = 0; i < (1 << n); ++i) {
cout << g[__builtin_popcount(i)][i] << " \n"[i == (1 << n) - 1];
}
return 0;
}
P12231 【模板】集合幂级数 ln / P13844 【模板】集合幂级数 ln(非素数模数)
考虑 \(\text{ln}\) 本质是 \(\text{exp}\) 的逆变换,组合意义就是,对于每个集合 \(S\) 告诉你它的所有划分方案的价值之和,求每个集合 \(S\) 的权值。
设 \(g_S\) 表示集合 \(S\) 的划分方案的价值和,\(f_S\) 表示集合 \(S\) 的权值,则容易得到递推式:
类似集合幂级数 \(\text{exp}\) 地做即可。时间复杂度 \(O(2^n n^2)\),同样支持任意模数。
P13844 代码:
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define mid ((l + r) >> 1)
#define MID ((l + r + 1) >> 1)
#define lowbit(x) ((x) & -(x))
using namespace std;
constexpr int N = 20;
#define Z unsigned long long
int n;
Z f[N + 1][1 << N], g[N + 1][1 << N], h[N + 1][1 << N];
void fwt(Z a[], Z f = 1) {
for (int w = 2, k = 1; w <= (1 << n); w <<= 1, k <<= 1) {
for (int i = 0; i < (1 << n); i += w) {
for (int j = 0; j < k; ++j) {
a[i + j + k] += a[i + j] * f;
}
}
}
}
void fwt2(Z a[], Z f = 1) {
for (int w = 2, k = 1; w <= (1 << n); w <<= 1, k <<= 1) {
for (int i = 0; i < (1 << n); i += w) {
for (int j = 0; j < k; ++j) {
if (lowbit(i + j) == lowbit(i + j + k)) {
a[i + j + k] += a[i + j] * f;
}
}
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < (1 << n); ++i) {
Z w;
cin >> w;
f[__builtin_popcount(i)][i] = w;
g[__builtin_popcount(i)][i] = w;
}
g[0][0] = 0;
for (int i = 0; i <= n; ++i) {
fwt(f[i]);
}
for (int i = 0; i < n; ++i) {
fwt2(g[i]);
for (int j = 1; i + j <= n; ++j) {
for (int k = 0; k < (1 << n); ++k) {
h[i + j][k] += f[j][k] * g[i][k];
}
}
fwt2(g[i], -1);
fwt2(h[i + 1], -1);
for (int j = 0; j < (1 << n); ++j) {
g[i + 1][j] -= h[i + 1][j];
}
}
for (int i = 0; i < (1 << n); ++i) {
cout << g[__builtin_popcount(i)][i] << " \n"[i == (1 << n) - 1];
}
return 0;
}

浙公网安备 33010602011771号