「解题报告」[ABC221H] Count Multiset
K8He
·
·
题解
校内学长搬的模拟赛题,赛时爆标了,来写一发。
首先为了方便,先不考虑相同元素出现次数的限制。
我们设 f_{i, j} 表示当前填了 i 个数,总和为 j 的方案数。
考虑一种奇妙的转移方式:
- 向当前序列里加上一个 0,即从 f_{i - 1, j} 转移过来。
- 将当前序列全部加 1,即从 f_{i, j - i} 转移过来。
第一种操作的例子是序列 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;
}
}
```