「解题报告」[ABC221H] Count Multiset

· · 题解

校内学长搬的模拟赛题,赛时爆标了,来写一发。

首先为了方便,先不考虑相同元素出现次数的限制。

我们设 f_{i, j} 表示当前填了 i 个数,总和为 j 的方案数。

考虑一种奇妙的转移方式:

第一种操作的例子是序列 2 2 3 加上一个 0 得到 0 2 2 3,由 f_{3, 7} 转移到 f_{4, 7};第二种操作的例子是 0 0 1 2 整体加 1 变为 1 1 2 3,由 f_{4, 3} 转移到 f_{4, 7}

接下来开始考虑相同元素出现次数的限制。

既然有限制就不能随意加零,那么第一种操作贡献就应该是 \sum_{k = 1}^{m}f_{i - k, j},但是我们不知道之前的 f_{i - k, j} 有几个零,这样就不能直接转移。

考虑设一个辅助的 g_{i, j} 表示当前填了 i 个数,总和为 j序列里不含有 0 的方案数。

用这个可以很方便地转移 $f_{i, j}$ 的第一种操作,即 $\sum_{k = 1}^{m}g_{i - k, j}$。 总结一下转移方程: $$ \begin{aligned} f_{i, j} &= f_{i, j - i} + \sum_{k = 1}^{m}g_{i - k, j}\\ g_{i, j} &= f_{i, j - i} \end{aligned} $$ 考虑前缀和优化,$O(n^2)$ 解决。 一份实现: ```cpp const ll N = 5010, P = 998244353; namespace SOLVE { ll n, m, f[N][N], g[N][N], sum[N][N]; inline ll rnt () { ll x = 0, w = 1; char c = getchar (); while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); } while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar (); return x * w; } inline void In () { n = rnt (), m = rnt (); return; } inline void Solve () { _for (i, 1, n) { if (i <= m) f[i][0] = 1; _for (j, 1, n) { if (j >= i) g[i][j] = f[i][j] = f[i][j - i]; sum[i][j] = (sum[i - 1][j] + g[i][j]) % P; f[i][j] = (f[i][j] + sum[i - 1][j] - sum[std::max (0ll, i - m - 1)][j] + P) % P; } } return; } inline void Out () { _for (i, 1, n) printf ("%lld\n", g[i][n]); return; } } ```