NOI.AC170 数数(计数DP)

文章目录

题目

题目描述
求有多少对 1 n 1∼n 的排列 ( 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\}
两个方案 ( a , b ) (a, b) ( a , b ) (a', b') 不同当且仅当存在 i i 使得 a i a i a_i≠a'_i b i b i b_i≠b'_i

输入
一行两个整数 n , m n, m

输出
一行一个整数表示答案。对 998244353 998244353 取模。

样例输入
3 8

样例输出
18

数据范围
对于 100 % 100\% 的数据, 1 n 50 1 \leq n \leq 50 1 m 1 0 9 1 \leq m \leq 10^9

分析

考虑对于排列 a = 1 , 2 , 3 , , n a = 1, 2, 3, \cdots, n ,有多少种 b b 满足上述要求,那么再乘 n ! n! 就是最终答

d p [ i ] [ j ] [ k ] dp[i][j][k] 表示, 1 i 1 ∼ i 中,有 j j 个还未匹配( a a b b 中未匹配的数应该是一样的),剩下的已匹配的 i j i - j max { a x , b x } \max\{a_x, b_x\} 之和为 k k ,的方案数。

数字 i i 要产生贡献的话,就只能匹配一个不小于它的数,那么转移方程也可以列了,按照数字 i i a , b a, b 中是否产生贡献分四种情况转移。下面举一个例子:
i i 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 ,原因是从前 i 1 i - 1 个数中未匹配的 j j 里面随便选一个跟 a a 中的 i i 匹配可以确保 i i 产生贡献,但是由于 b b 中的 i i 未被匹配,因此未匹配的对数还是 j j

其他情况可以看注释。

最后,尽管 m m 很大,但是粗略估计 m m 最多 n 2 n^2 ,大于 n 2 n^2 无解,枚举一下统计答案即可。

代码

#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));
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/105158205