Loading

【题解】P2592 - [ZJOJ2008] 生日聚会

题目大意

例题链接

已知现在有 \(n\) 个男孩和 \(m\) 个女孩,一种男女排列顺序合法时,当且仅当 \(n + m\) 个位置里任意连续的一段中,男女人数的差不超过 \(k\)。试求这 \(n + m\) 个人的合法排列方案总数。

\(n, m \leq 150, k \leq 20\)

解题思路

抓住题面给出的重要信息 方案总数 ,自然能想到这道题要用 动态规划 求解。问题在于,这道题的状态不是特别容易定义。容易想到用 \(dp_{i, j, k}\) 表示前 \(i\) 个人中有 \(j\) 个男孩,\(k\) 个女孩的方案总数。接着分类讨论,进行相应的更新。这样定义状态的弊端在于,无法简便地维护任意一段中男女的人数差,也就无法确定第 \(i\) 个位置是否可以放男生或女生。

可以发现,某一个位置只可能是男生或女生,所以知道了前 \(i\) 个人中有 \(j\) 个男生,自然就能求出前 \(i\) 个人中有 \(i - j\) 个女生。接下来,我们需要表示出 人数差 这个信息。因为前 \(i\) 个位置中,任意一段后缀都对应着原序列的一个连续区间。假如前 \(1\) 个位置的后缀满足条件,前 \(2\) 个位置的后缀满足条件……前 \(n\) 个位置的后缀满足条件,原序列中的任意一段区间也一定满足条件。因此,我们可以想到用一个三维的状态 \(dp_{i, j, k}\) 表示前 \(i\) 个人里有 \(j\) 个男生,\(i - j\) 个女生且男女最大人数差不超过 \(k\) 的方案总数。

但是,这样做好像还是不能令状态转移更加简便:我们无从得知第 \(i\) 个位置会令男生与女生的人数差 \(+1\),还是令女生与男生的人数差 \(+1\),也就确定不了第三维的状态。所以,我们还需要将第三维的状态拆分成两维,得到 \(dp_{i, j, k, l}\) 表示前 \(i + j\) 个人里放 \(i\) 个男生,\(j\) 个女生且男生减女生的差不超过 \(k\),女生减男生的差不超过 \(l\) 的方案总数。

由此可以得到状态转移方程。当第 \(i + j\) 个位置放了男生时,所有的后缀都会在末尾增加一个男生,男生减女生的差就会 \(+ 1\),女生减男生的差就会 \(- 1\)。第 \(i + j\) 个位置放女生同理。此处,我们用 刷表法 更新状态会更加简便:

当第 \(i + j\) 个位置放男生时,\(dp[i + 1][j][k + 1][\max(l - 1, 0)] = dp[i + 1][j][k + 1][\max(l - 1,0)] + dp[i][j][k][l]\);当第 \(i + j\) 个位置放女生时,\(dp[i][j + 1][\max(k - 1, 0)][l + 1] = dp[i][j + 1][\max(k - 1, 0)][l + 1] + dp[i][j][k][l]\)

至于为什么要和 \(0\)\(\max\),此处联系上述状态的定义。以 \(l\) 为例,当 \(l = 0\) 时,说明任何一个后缀内,男生的人数都一定大于等于女生的人数。所以当男生增加一个人时,男生的人数还是一定大于等于女生的人数,也就是女生减男生的人数不会大于 \(0\)。因为 \(k\)\(l\) 的变化是 对称的,所以我们最后累加答案等于 \(\sum\limits_{i = 0}^k \sum\limits_{j = 0}^k dp_{n, m, i, j}\) 时,统计到的答案中,男生和女生的最大人数差一定不会超过 \(k\)

也就是每当男生减去女生的人数差 \(+ 1\) 的时候,女生减男生的人数差就会 \(- 1\)。当男生和女生的人数差不超过 \(k\) 的时候,女生与男生的人数差也一定不会超过 \(-k\)。所以人数差 \(d < k = (-k) = \lvert k \rvert\),合法。

另,实际计算的时候 记得取模,本文因美观限制不再加上取模部分,请读者自行完成

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 155
#define maxk 25
#define mod 12345678

int n, m, K;
int dp[maxn][maxn][maxk][maxk];

int main()
{
	scanf("%d%d%d", &n, &m, &K);
	dp[0][0][0][0] = 1;
	for (int i = 0; i <= n; i++)
	{
		for (int j = 0; j <= m; j++)
		{
			for (int k = 0; k <= K; k++)
			{
				for (int l = 0; l <= K; l++)
				{
					dp[i + 1][j][k + 1][max(l - 1, 0)] += dp[i][j][k][l];
					dp[i][j + 1][max(k - 1, 0)][l + 1] += dp[i][j][k][l];
					dp[i + 1][j][k + 1][max(l - 1, 0)] %= mod;
					dp[i][j + 1][max(k - 1, 0)][l + 1] %= mod;
				}
			}
		}
	}
	int ans = 0;
	for (int i = 0; i <= K; i++)
		for (int j = 0; j <= K; j++)
			ans = (ans + dp[n][m][i][j]) % mod;
	printf("%d\n", ans);
	return 0;
}
posted @ 2021-07-24 23:27  kymru  阅读(36)  评论(0编辑  收藏  举报