题目
题目描述
求有多少对
1
∼
n
1∼n
1∼n的排列
(
a
,
b
)
(a, b)
(a,b)满足
m
≤
∑
i
=
1
n
max
{
a
i
,
b
i
}
m \leq \sum\limits_{i = 1}^{n} \max\{a_i,b_i\}
m≤i=1∑nmax{ai,bi}。
两个方案
(
a
,
b
)
(a, b)
(a,b)和
(
a
′
,
b
′
)
(a', b')
(a′,b′)不同当且仅当存在
i
i
i使得
a
i
≠
a
i
′
a_i≠a'_i
ai=ai′或
b
i
≠
b
i
′
b_i≠b'_i
bi=bi′。
输入
一行两个整数
n
,
m
n, m
n,m。
输出
一行一个整数表示答案。对
998244353
998244353
998244353取模。
样例输入
3 8
样例输出
18
数据范围
对于
100
%
100\%
100%的数据,
1
≤
n
≤
50
1 \leq n \leq 50
1≤n≤50,
1
≤
m
≤
1
0
9
1 \leq m \leq 10^9
1≤m≤109。
分析
考虑对于排列 a = 1 , 2 , 3 , ⋯ , n a = 1, 2, 3, \cdots, n a=1,2,3,⋯,n,有多少种 b b b满足上述要求,那么再乘 n ! n! n!就是最终答
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示, 1 ∼ i 1 ∼ i 1∼i中,有 j j j个还未匹配( a a a和 b b b中未匹配的数应该是一样的),剩下的已匹配的 i − j i - j i−j个 max { a x , b x } \max\{a_x, b_x\} max{ax,bx}之和为 k k k,的方案数。
数字
i
i
i要产生贡献的话,就只能匹配一个不小于它的数,那么转移方程也可以列了,按照数字
i
i
i在
a
,
b
a, b
a,b中是否产生贡献分四种情况转移。下面举一个例子:
当
i
i
i在
a
a
a中产生贡献时,
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
i
]
[
j
]
[
k
]
+
d
p
[
i
−
1
]
[
j
]
[
k
−
i
]
×
j
dp[i][j][k] = dp[i][j][k] + dp[i - 1][j][k - i] \times j
dp[i][j][k]=dp[i][j][k]+dp[i−1][j][k−i]×j,原因是从前
i
−
1
i - 1
i−1个数中未匹配的
j
j
j里面随便选一个跟
a
a
a中的
i
i
i匹配可以确保
i
i
i产生贡献,但是由于
b
b
b中的
i
i
i未被匹配,因此未匹配的对数还是
j
j
j。
其他情况可以看注释。
最后,尽管 m m m很大,但是粗略估计 m m m最多 n 2 n^2 n2,大于 n 2 n^2 n2无解,枚举一下统计答案即可。
代码
#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));
}