题解:P13844 集合幂级数 ln(非素数模数)
题意
给定集合幂级数 \(F(x)\),对于每个 \(S\subseteq \{1,2,\cdots,n\}\),求 \([x^S]\ln(F(x))\),答案对 \(\mathbf{2^{64}}\) 取模。
题解
前情提要:P13843。
传统做法是对占位多项式做 \(\ln\),需要求逆元,不适用于本题。
我们知道 \(\exp\) 的递推式:
而 \(\ln\) 是 \(\exp\) 的逆运算,考虑从 \(\exp\) 的递推式倒推出 \(\ln\) 的递推式。
注意到我们总有 \(f_{\varnothing}=1\),因此
这样我们就得到 \(\ln\) 的递推式了。
从泰勒展开的角度来看,这个递推式其实也有迹可循:
我们发现,这个式子可以看作给每一个大小为 \(k\) 的无序拆分赋以 \((-1)^{k-1}(k-1)!\) 的权值。形式化来说:
回到上面推出递推式,可以看出,\(\sum\) 前面的负号就是乘上了 \(-1\) 的系数,而转移时钦定 \(\min(T)=\min(S)\) 相当于枚举某个划分出的集合 \(S-T\),对于一个大小为 \(k\) 的拆分,这个集合有 \(k-1\) 种选法,乘在一起自然也就带上了 \((k-1)!\) 的系数。
暴力做上面的递推式是 \(\mathcal{O}(3^n)\),考虑如何优化。类比 \(\exp\) 的做法,我们还是将集合按 \(\min(S)\) 从大到小分层,当枚举到 \(\min(S)=i\) 时,当前层的转移可以写作:
显然这是半在线子集卷积的形式,我们在每一层做一次半在线子集卷积求出当前层的 DP 值即可。
注意到在枚举到 \(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); }
namespace IO {
const int S = 1 << 24, lm = 1 << 23;
char bi[S + 5], *p1 = bi, *p2 = bi, ch;
#define gc() (p1 == p2 && (p2 = (p1 = bi) + fread(bi, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)
inline ull rd() {
char ch;
while (ch = gc(), (ch < '0'));
ull x = ch ^ 48;
while (ch = gc(), (ch >= '0')) x = (x << 3) + (x << 1) + (ch ^ 48);
return x;
}
}
using IO::rd;
int n, pc[N];
ull a[N], f[N];
ull F[21][N], G[21][N], H[21][N];
void OR(ull *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)
a[i ^ j ^ k] += a[i ^ j] * tp;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
n = rd();
for (int i = 0; i < 1 << n; ++i) a[i] = rd();
for (int s = 1; s < 1 << n; ++s) pc[s] = pc[s ^ lowbit(s)] + 1;
f[1 << n - 1] = a[1 << n - 1];
for (int i = n - 2; ~i; --i) {
int sz = n - i - 1;
fill(F[0], F[0] + (1 << sz), 0), f[1 << i] = F[0][0] = a[1 << i];
OR(F[0], sz, 1);
for (int j = 1; j <= sz; ++j) fill(G[j], G[j] + (1 << sz), 0);
for (int s = 1; s < 1 << sz; ++s) G[pc[s]][s] = a[s << i + 1];
for (int j = 1; j <= sz; ++j) OR(G[j], sz, 1);
for (int j = 1; j <= sz; ++j) {
fill(F[j], F[j] + (1 << sz), 0);
for (int k = 0; k < j; ++k) for (int s = 0; s < 1 << sz; ++s)
F[j][s] += F[k][s] * G[j - k][s];
OR(F[j], sz, -1);
for (int s = 1; s < 1 << sz; ++s)
if (pc[s] == j) f[(s << i + 1) ^ (1 << i)] = F[j][s] = a[(s << i + 1) ^ (1 << i)] - F[j][s];
else F[j][s] = 0;
if (j < sz) OR(F[j], sz, 1);
}
}
for (int s = 0; s < 1 << n; ++s) cout << f[s] << ' ';
return 0;
}

浙公网安备 33010602011771号