ABC221H Count Multiset
[ABC221H] Count Multiset
以下内容多引用自 [1] 对应的文章
分拆数
表示将 正整数 \(N\) 拆成 若干正整数 和的 方案数 \(P_N\),可以形式化的表示成 以下方程的解的个数
其中我们通常将每个正整数 \(x_i\) 称作 一个部分
注意到 分拆数 的定义中 不要求顺序,这里为方便运算 人为规定了大小顺序,不影响正确性
注意到 分拆数 严格意义上形式化为 不升序列的和,但为方便书写,以下采用 不降序列和 的形式
这里给出前面分拆数的一个表
| \(N\) | \(0\) | \(1\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(7\) | \(8\) | \(9\) | \(10\) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| \(P_N\) | \(1\) | \(1\) | \(2\) | \(3\) | \(5\) | \(7\) | \(11\) | \(15\) | \(22\) | \(30\) | \(42\) |
\(K\) 部分拆数
即表示将 正整数 \(N\) 拆成 \(K\) 个正整数 和的 方案数 \(P_{N, K}\),可以形式化的表示成 以下方程的解的个数
我们可以注意到存在
改写一下 上面的方程,有
于是当 \(x_1, x_2, ... , x_K\) 中有 \(i\) 个数 非 \(0\) 时,显然方程将有 \(P_{N - K, i}\) 个解,于是有下式
于是 相邻两式做差有
即有如下递推式
于是我们可以在 \(O(N ^ 2)\) 的复杂度内求得任意 \(P_{N, K}\)
for (int i = 1; i <= N; ++ i)
for (int j = 1; j <= i; ++ j)
F[i][j] = F[i - 1][j - 1] + F[i - j][j]
\(Ferrers\) 图
上面的式子应当有一些 组合意义,我们可以这样描述 其中一种
显然我们需要构造一个 不降序列 \(x_1, x_2, ... , x_k\) 使得其 和为 \(k\)
构造该序列 一共只需要下面两步
- 在序列前方插入 \(1\),\(eg. ~ 2 ~ 3 ~ 4 \to 1 ~ 2 ~ 3 ~ 4\)
- 给序列整体加 \(1\),\(eg. ~ 2 ~ 3 ~ 4 \to 3 ~ 4 ~ 5\)
可以证明,所有的 不降序列 都可以通过 上述两步执行若干次 后得到
同时其对于 整个序列的数和 的影响分别是 \(+1\) 与 \(+N\)(\(N\) 为 序列项数)
对于 整个序列的项数 的影响分别是 \(+1\) 与 \(+0\)
回到递推式 \(P_{N, K} = P_{N - 1, K - 1} + P_{N - K, K}\),现在就好理解了
\(P_{N - 1, K - 1}\) 代表的就是 执行 \(1\) 操作后 可以得到合法序列的种数
\(P_{N - K, K}\) 代表的就是 执行 \(2\) 操作后 可以得到合法序列的种数
两者求和,正确性显然
而这里也有一种 思路相似 的 图形转化 方式,即 \(Ferrers\) 图
我们将分拆的 每部分 用 点行 表示,每行点数 就是 这个部分的大小
根据其 严格定义,\(Ferrers\) 图 按点数递减次序摆放,最长的一行在顶部
\(eg. ~ 10 = 5 + 2 + 2 + 1\) 的 \(Ferrers\) 图
⚪ ⚪ ⚪ ⚪ ⚪
⚪ ⚪
⚪ ⚪
⚪
将一个 \(Ferrers\) 沿 左上 - 右下 对角线 反转后 将得到新的 \(Ferreres\) 图,也称为 原图的共轭
\(eg. ~ 10 = 5 + 2 + 2 + 1\) 的 \(Ferrers\) 图的共轭(即 \(10 = 4 + 3 + 1 + 1 + 1\) 的 \(Ferrers\) 图)
⚪ ⚪ ⚪ ⚪
⚪ ⚪ ⚪
⚪
⚪
⚪
我们可以由此得到分拆数的 一个性质
自然数 \(N\) 的 最大部分为 \(K\) 的 分拆数个数 等于 \(P_{N, K}\),即 \(N\) 的 \(K\) 部分拆数
同时,我们可以在 \(Ferrers\) 图上 形象化 刚刚 构造不降序列 的 两个操作
-
在序列前面插入 \(1\),即是 在图下方插入一个点,行数 \(+1\)
\(eg. ~ 2 ~ 3 ~ 4 \to 1 ~ 2 ~ 3 ~ 4\)
. . . . . . . . . . . -> . . . . . . . . -
给序列整体 \(+1\),即是 在图左边插入一列,行数不变
\(eg. ~ 2 ~ 3 ~ 4 \to 3 ~ 4 ~ 5\)
. . . . . . . . . . . . -> . . . . . . . . .
于是操作反过来就是 砍掉下面一个独立点 或 砍掉左边一列(没有独立点时)
而递推式 \(P_{N, K} = P_{N - 1, K - 1} + P_{N - K, K}\) 即对应如下
⚪ ⚪ ... ⚪ ⚪
⚪ ⚪ ... ⚪ ⚪
...
⚪ ⚪
⚪
⚪
共 N 个点,K 行
⚪ ⚪ ... ⚪ ⚪
⚪ ⚪ ... ⚪ ⚪
...
⚪ ⚪
⚪
砍独立点,剩 N - 1 个点,K - 1 行
⚪ ... ⚪ ⚪
⚪ ... ⚪ ⚪
...
⚪
砍左一列,剩 N - K 个点,K 行(没有独立点时)
注意到 实际上 是 \(N - K\) 个点,\(K\) 行,左边插入一列 得到的 \(N\) 点 \(K\) 行
故显然对应情况 没有独立点,正确性不受影响
回到原题
我们发现无非是 \(K\) 部分拆数 多了一个 每个元素出现次数不超过 \(M\) 的要求
想想我们 不降序列 的构造过程,整体 \(+1\) 显然 不影响相同元素出现次数的最大值
也就是只有 插入 \(1\) 这一步 可能导致相同元素出现超过 \(M\) 次
容易发现,我们只需要保证(插入 \(1\))后,\(1\) 出现次数不超过 \(M\) 即可
正难则反,考虑容斥,也就是将 所有情况求出后 减去 \(1\) 出现 \(M + 1\) 次的情况
注意到 \(1\) 是 一个一个插入的,也就是我们只需保证 \(1\) 不会出现 \(M + 1\) 次即可
不用考虑出现 \(M + 2...M + N\) 次的情况
回到 \(Ferrers\) 图,我们发现如果存在 \(M + 1\) 个 \(1\),则最左边将有 \(M + 1\) 个独立点
此时我们 暴力将最左边一列 砍掉,显然总点数少了 \(K\),行数少了 \(M + 1\)
于是新图对应的分拆数即是 \(P_{N - K, K - (M + 1)}\),将这部分减掉就是正确的递推式了
给个代码
#include <bits/stdc++.h>
const int MAXN = 5005;
const int MOD = 998244353;
using namespace std;
long long F[MAXN][MAXN];
int N, M;
int main () {
cin >> N >> M;
F[0][0] = 1;
for (int i = 1; i <= N; ++ i)
for (int j = 1; j <= i; ++ j)
F[i][j] = (F[i - 1][j - 1] + F[i - j][j] - (j > M ? F[i - j][j - (M + 1)] : 0) + MOD) % MOD;
for (int i = 1; i <= N; ++ i) cout << F[N][i] << '\n';
return 0;
}

浙公网安备 33010602011771号