题解:P13843 集合幂级数 exp(非素数模数)

题意

给定集合幂级数 \(F(x)\),对于每个 \(S\subseteq \{1,2,\cdots,n\}\),求 \([x^S]\exp(F(x))\),答案对 \(\mathbf{2^{64}}\) 取模。

题解

传统做法是对占位多项式做 \(\exp\),需要求逆元,不适用于本题。

考察 \(\exp(F(x))\) 的泰勒展开:

\[\exp(F(x))=\sum_{k=0}^{+\infty}\frac{F(x)^k}{k!} \]

其中乘法是子集卷积。

考虑组合意义,我们知道 \([x^S]F(x)^k\) 的意义是把 \(S\) 有序地划分成 \(k\) 个集合的方案数(划分出的某个集合 \(T\) 内部的方案数为 \([x^T]F(x)\)),那么 \(\dfrac{F(x)^k}{k!}\) 的意义就是无序地划分成 \(k\) 个集合的方案数。因此 \(\exp\) 的组合意义就是对集合做无序划分的方案数,形式化来说:

\[[x^S]\exp(F(x))=\sum_{\substack{\{S_1,\cdots,S_k\}\\S_1\cup\cdots\cup S_k=S\\ |S_1|+\cdots+|S_k|=|S|}}\prod_{i=1}^k[x^{S_i}]F(x) \]

这个意义很优美,考虑直接对着这个组合意义 DP:设 \(a_S=[x^S]F(x)\),令 \(f_S\) 表示对 \(S\) 做无序划分的方案数。转移时枚举 \(\min(S)\) 所在的划分出的集合 \(T\)

\[f_S=\sum_{\substack{T\subseteq S\\\min(T)=\min(S)}} a_Tf_{S-T} \]

暴力做是 \(\mathcal{O}(3^n)\) 的。

进一步优化,考虑将 \(S\) 按照 \(\min(S)\) 从大到小分层转移,枚举到 \(\min(S)=i\) 时,当前层的转移可以写作

\[f_{S\cup\{i\}}=\sum_{T\subseteq S}a_{T\cup\{i\}}f_{S-T} \]

显然 \(\min(T)>i\)\(\min(S-T)>i\),而上式就是子集卷积的形式,于是我们在每一层做一次子集卷积即可得到当前层的 DP 值。

时间复杂度看似是 \(\mathcal{O}(2^nn^3)\),但是注意到在枚举到 \(i\) 时,全集为 \(U=\{i+1,\cdots,n\}\),因此实际的时间复杂度 \(\mathcal{O}(\sum_{i=0}^{n-1}2^ii^2)=\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;

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); }

int n, pc[N];
ull a[N], f[N];
ull F[N][21], G[N][21], H[N][21];

void OR(ull a[N][21], 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)
			for (int p = 0; p <= n; ++p) a[i ^ j ^ k][p] += a[i ^ j][p] * tp;
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n;
	for (int i = 0; i < 1 << n; ++i) cin >> a[i];
	for (int s = 1; s < 1 << n; ++s) pc[s] = pc[s ^ lowbit(s)] + 1;
	f[0] = 1, f[1 << n - 1] = a[1 << n - 1];
	for (int i = n - 2; ~i; --i) {
		int sz = n - 1 - i;
		for (int s = 0; s < 1 << sz; ++s) {
			fill(F[s], F[s] + sz + 1, 0), F[s][pc[s]] = f[s << i + 1];
			fill(G[s], G[s] + sz + 1, 0), G[s][pc[s]] = a[(s << i + 1) ^ (1 << i)];
		}
		OR(F, sz, 1), OR(G, sz, 1);
		for (int s = 0; s < 1 << sz; ++s) {
			fill(H[s], H[s] + sz + 1, 0);
			for (int i = 0; i <= sz; ++i) for (int j = 0; j <= i; ++j)
				H[s][i] += F[s][j] * G[s][i - j];
		}
		OR(H, sz, -1);
		for (int s = 0; s < 1 << sz; ++s) f[(s << i + 1) ^ (1 << i)] = H[s][pc[s]];
	}
	for (int s = 0; s < 1 << n; ++s) cout << f[s] << ' ';
	return 0;
}
posted @ 2026-01-10 18:01  P2441M  阅读(2)  评论(0)    收藏  举报