题目
题目描述
求有多少对
的排列
满足
。
两个方案
和
不同当且仅当存在
使得
或
。
输入
一行两个整数
。
输出
一行一个整数表示答案。对
取模。
样例输入
3 8
样例输出
18
数据范围
对于
的数据,
,
。
分析
考虑对于排列 ,有多少种 满足上述要求,那么再乘 就是最终答
表示, 中,有 个还未匹配( 和 中未匹配的数应该是一样的),剩下的已匹配的 个 之和为 ,的方案数。
数字
要产生贡献的话,就只能匹配一个不小于它的数,那么转移方程也可以列了,按照数字
在
中是否产生贡献分四种情况转移。下面举一个例子:
当
在
中产生贡献时,
,原因是从前
个数中未匹配的
里面随便选一个跟
中的
匹配可以确保
产生贡献,但是由于
中的
未被匹配,因此未匹配的对数还是
。
其他情况可以看注释。
最后,尽管 很大,但是粗略估计 最多 ,大于 无解,枚举一下统计答案即可。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
const int MAXN = 50;
const int MOD = 998244353;
inline int Add(int x, const int y) {
x += y; if (x > MOD) x -= MOD; return x;
}
inline int Mul(int x, const int y) {
x = (long long)x * y % MOD; return x;
}
int N, M;
int Dp[MAXN + 5][MAXN + 5][MAXN * MAXN + 5];
int main() {
// freopen("sum.in", "r", stdin);
// freopen("sum.out", "w", stdout);
scanf("%d%d", &N, &M);
if (M > N * N)
return puts("0"), 0;
Dp[0][0][0] = 1;
for (int i = 1; i <= N; i++) { // 对于a / b里面所有小于等于 i 的数
for (int j = 0; j <= i; j++) { // a / b 里面小于等于 i 的数中各有 j 个数还未匹配
for (int k = 0; k <= N * N; k++) { // ∑ max(a[x], b[x]) = k
int &cur = Dp[i][j][k];
if (j >= 1)
cur = Add(cur, Dp[i - 1][j - 1][k]);
// 1. a / b 中 i 均不产生贡献 => 多了一个未配对
if (k >= i && j <= i - 1) {
cur = Add(cur, Mul(Dp[i - 1][j][k - i], j));
// 2. a 中产生贡献 => 未配对数不变, 从小于等于 i - 1 的 j 个未匹配数中随便选一个和 i 匹配
cur = Add(cur, Mul(Dp[i - 1][j][k - i], j + 1));
// 3. b 中产生贡献 => 未配对数不变, 与上式同理, 但考虑到 a[x] = b[x] = i 的情况, 只在从此处 + 1 而上面不 + 1
}
if (k >= 2 * i)
cur = Add(cur, Mul(Dp[i - 1][j + 1][k - 2 * i], Mul(j + 1, j + 1)));
// 4. a / b 中均产生贡献 => 之前的状态要多一个未配对才行, 两个各自匹配故均为 j + 1 种情况
}
}
}
int Ans = 1, Tot = 0;
for (int i = 1; i <= N; i++)
Ans = Mul(Ans, i);
for (int i = M; i <= N * N; i++)
Tot = Add(Tot, Dp[N][0][i]);
printf("%d", Mul(Ans, Tot));
}