题解:P10461 多项式复合集合幂级数
题意
给定集合幂级数 \(F(x)\) 和一个 \(n\) 次多项式 \(G(x)\),对于每个 \(S\subseteq \{1,2,\cdots,n\}\),求 \([x^S]G(F(x))\)。\(1\leq n\leq 20\)。
题解
这个 DP 也是没有人类了。
显然有
考虑其组合意义,相当于对 \(S\) 做无序拆分,拆分出的每个集合 \(S_i\) 内部的方案数为 \([x^{S_i}]F(x)\),大小为 \(i\) 的拆分会乘上 \(g_ii!\) 的权值,求所有拆分的权值总和。
套路地给拆分钦定顺序,按照 \(\max(S)\) 从小到大加入集合。考虑 DP,比较正常的想法是,令 \(h_{i,j,S}\) 表示考虑到 \(\max(S)\leq i\) 的 \(S\),拆分成 \(j\) 个部分的权值和。不难得出转移:
- 不加入 \(\max(S)=i+1\) 的集合:\(h_{i,j,S}\to h_{i+1,j,S}\)。
- 加入 \(\max(S)=i+1\) 的集合:设 \([x^S]F_i(x)=[\max(S)=i][x^S]F(x)\),则 \(h_{i,j}\times F_{i+1}\to h_{i+1,j+1}\),其中乘法为子集卷积。
统计答案时,计算 \([x^S]G(F(x))=\sum\limits_{i=0}^n h_{n,i,S}g_ii!\) 即可。枚举到 \(i\) 时,需要做 \(i\) 次规模为 \(2^i\) 的子集卷积,时间复杂度为 \(\mathcal{O}(\sum_{i=1}^n 2^ii^3)=\mathcal{O}(2^nn^3)\),无法通过。
考虑一个极其智慧的处理,我们改变 \(j\) 的含义,将状态定义改为:令 \(h_{i,j,S}\) 表示考虑到 \(\max(S)\leq i\) 的 \(S\),还需要加入 \(j\) 个部分的权值和。转移是类似的:
- 不加入 \(\max(S)=i+1\) 的集合:\(h_{i,j,S}\to h_{i+1,j,S}\)。
- 加入 \(\max(S)=i+1\) 的集合:\(h_{i,j}\times F_{i+1}\to h_{i+1,j-1}\)。
为什么要做这样的处理?我们发现改变状态定义后,会有 \(0\leq j\leq n-i\),因此枚举到 \(i\) 时,只需要做 \(\bm{n-i}\) 次规模为 \(2^i\) 的子集卷积,时间复杂度是 \(\mathcal{O}(\sum_{i=1}^n(n-i)2^ii^2)=\mathcal{O}(2^nn^2)\) 的!
时间复杂度的证明
设 \(f(n)=\sum\limits_{i=1}^n(n-i)2^ii^2\),则有
因此 \(f(n)=\mathcal{O}(2^nn^2)\)。
但是改变状态后会有一个问题:如何统计答案?最终状态 \(h_{n,0,S}\) 所包含的拆分方案大小是不同的,貌似有点难办。其实,我们只需要在赋初值时令 \(h_{0,i,\varnothing}=g_ii!\) 就行了,这样对应大小的拆分就自然带上了正确的系数。这样 \(h_{n,0}\) 就是我们所求的集合幂级数了。
于是我们就 \(\mathcal{O}(2^nn^2)\) 地解决了此题。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
const int N = 1 << 20, MOD = 998244353;
template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chk_min(T &x, T y) { x = min(x, y); }
template<typename T> void chk_max(T &x, T y) { x = max(x, y); }
template<typename T> T add(T x, T y) { return x += y - MOD, x += x >> 31 & MOD; }
template<typename T> T sub(T x, T y) { return x -= y, x += x >> 31 & MOD; }
template<typename T> void cadd(T &x, T y) { x += y - MOD, x += x >> 31 & MOD; }
template<typename T> void csub(T &x, T y) { x -= y, x += x >> 31 & MOD; }
int n, f[N], g[21], pc[N];
int h[21][N], F[N][21];
void OR(auto &a, int n, int tp) {
for (int k = 1; k < 1 << n; k <<= 1)
for (int i = 0; i < 1 << n; i += k << 1) for (int j = 0; j < k; ++j) {
int *A = a[i ^ j ^ k], *B = a[i ^ j];
for (int p = 0; p <= n; ++p) (tp == 1 ? cadd<int> : csub<int>)(A[p], B[p]);
}
}
void mul(int *a, int *res, int n) {
static int b[N][21], tmp[21];
for (int s = 0; s < 1 << n; ++s) fill(b[s], b[s] + n + 1, 0), b[s][pc[s]] = a[s];
OR(b, n, 1);
for (int s = 0; s < 1 << n; ++s) {
fill(tmp, tmp + n + 1, 0);
int *A = b[s], *B = F[s];
for (int i = 0; i <= n; ++i) {
int val = 0;
for (int j = 0; j <= i; ++j) cadd<int>(val, (ll)A[j] * B[i - j] % MOD);
tmp[i] = val;
}
copy(tmp, tmp + n + 1, b[s]);
}
OR(b, n, -1);
for (int s = 0; s < 1 << n; ++s) cadd(res[s], b[s][pc[s]]);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int s = 1; s < 1 << n; ++s) pc[s] = pc[s ^ lowbit(s)] + 1;
for (int s = 0; s < 1 << n; ++s) cin >> f[s];
for (int i = 0; i <= n; ++i) cin >> g[i];
for (int i = 0, fc = 1; i <= n; ++i)
h[i][0] = (ll)g[i] * fc % MOD, fc = (ll)fc * (i + 1) % MOD;
for (int i = 0; i < n; ++i) {
for (int s = 0; s < 1 << i; ++s) F[s][pc[s]] = f[s ^ (1 << i)];
OR(F, i, 1);
for (int j = 1; j <= n - i; ++j) mul(h[j], h[j - 1] + (1 << i), i);
for (int s = 0; s < 1 << i; ++s) fill(F[s], F[s] + i + 1, 0);
}
for (int s = 0; s < 1 << n; ++s) cout << h[0][s] << ' ';
return 0;
}

浙公网安备 33010602011771号