题解 P6097 【【模板】子集卷积】

皎月半洒花

2020-02-14 18:24:37

Solution

萌新由于太弱被卡了常。~~那一定是FMT自身常数太大了~~ _____ 如果不会 FMT 可以移步谷谷的板子题或者[我的博客](https://www.orchidany.cn/2019/08/26/fmt/) 快速子集变换(FST)是在 FMT 基础上的扩展,解决的也是子集交卷积,但是限制了状态不重复,即 $$c_i=\sum_{\begin{aligned}~k &\cup j=i\\ k &\cap j= \emptyset \end{aligned}}a_kb_j$$ 换个写法: $$ c_s=\sum_{t\subseteq s}a_sb_{s-t} $$ 这东西其实也不是非要用 FMT 来做,FWT 也可以。 然后就是考虑在卷积的时候多增加一维,即 $f_{i,S}$ 表示集合 $S$ 中有 $i$ 个元素,于是发现只有当元素个数相加符合时才是对的。 于是一开始将 $f_{bct(s),s}$ 赋值为 $f_s$,其中 $bct(s)=\rm bitcount(s)$。然后对每一个 $f_i$ 分别做 FMT,之后按位乘的时候需要 $$P_{i, S}=\sum_{i=0}^{i} f_{j, S} \times g_{i-j, S}$$ 输出的时候只输出 $P_{bct(s),s}$ 即可。 复杂度似乎是两个 $\log$ 的?然而并不满。推荐取模优化。 ```cpp #define MAXN 21 #define MAXM 2000010 #define Mod 1000000009 #define I inline #define LL long long using namespace std; int bc[MAXM]; int N, M; int A[MAXN][MAXM], B[MAXN][MAXM], C[MAXN][MAXM]; I int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } I void reduce(int &x) { x += x >> 31 & Mod; } I void Ifmt(int *f) { register int i, j; for (i = 0; i < N; ++i) for (j = 0; j <= M; ++j) if (j >> i & 1) reduce(f[j] -= f[j ^ 1 << i]); } I void fmt(int *f) { register int i, j; for (i = 0; i < N; ++i) for (j = 0; j <= M; ++j) if (j >> i & 1) reduce(f[j] += f[j ^ 1 << i] - Mod); } int main() { cin >> N; M = (1 << N) - 1; register int i, j, s; for (i = 1; i <= M; ++i) bc[i] = bc[i - (i & -i)] + 1; for (i = 0; i <= M; ++i) A[bc[i]][i] = read(); for (i = 0; i <= M; ++i) B[bc[i]][i] = read(); for (i = 0; i <= N; ++i) fmt(A[i]), fmt(B[i]); for (i = 0; i <= N; ++i) for (j = 0; j <= i; ++j) for (s = 0; s <= M; ++s) (C[i][s] += 1ll * A[j][s] * B[i - j][s] % Mod) %= Mod; for (i = 0; i <= N; ++i) Ifmt(C[i]); for (i = 0; i <= M; ++i) printf("%d ", C[bc[i]][i]); } ```