23.7.24 牛客暑期多校3部分题解

B - Auspiciousness

题目大意

你一个牌堆,其中有 2 n 2n 2n 张牌,排面为 1 , 2 , … , 2 n 1,2,…,2n 1,2,,2n,需要进行如下操作:

  1. 从牌堆顶取走一张牌

  2. 如果这张牌大于 n n n 则猜测下一张小于这张牌,否则猜测下一张大于这张牌

  3. 取走下一张牌判断与猜测情况是否相同,相同则回到2操作继续,不相同则结束操作

问在所有 ( 2 n ) ! (2n)! (2n)! 种情况中一共能取几张牌(对输入的 m m m 取模)

解题思路

先模拟一下,发现最终摸到牌的序列一定是小数上升序列和大数下降序列的交替

考虑pd,用 f i , j f_{i,j} fi,j 表示已经取 i i i 张小于等于 n n n j j j 张大于 n n n 的牌的合法方案数

再加一位01状态用 0 0 0 表示前一段为小数,用 1 1 1 表示前一段为大数

转移的时候枚举上一段长度进行专一即可,要注意第一段前面没有数,所以要特判处理

code

#include 
using namespace std;
const int N = 305;
int t, n, m;
long long fac[2 * N], c[N][N], f[N][N][2], sum;
int main() {
	scanf("%d", &t);
	while (t --) {
		scanf("%d%d", &n, &m);
		fac[0] = c[0][0] = 1;//阶乘和组合数预处理
		for (int i = 1; i <= 2 * n; ++ i)
			fac[i] = fac[i - 1] * i % m, c[i][0] = 1;
		for (int i = 1; i <= n; ++ i)
			for (int j = 1; j <= i; ++ j)
				c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % m;
		for (int i = 0; i <= n; ++ i)
			for (int j = 0; j <= n; ++ j)
				f[i][j][0] = f[i][j][1] = 0;
		sum = fac[2 * n];//有(2n)!种情况,先拿了第一张
		for (int h = 1; h < 2 * n; ++ h)
			for (int i = max(h - n, 0); i <= min(h, n); ++ i) {
				int j = h - i;
				for (int k = 1; k <= i; ++ k)//放k张牌的方案是在当前类剩余的牌中选择k张
					f[i][j][1] = (f[i][j][1] + f[i - k][j][0] * c[n - i + k][k] % m) % m;
				for (int k = 1; k <= j; ++ k)
					f[i][j][0] = (f[i][j][0] + f[i][j - k][1] * c[n - j + k][k] % m) % m;
				if (i == h) f[i][j][1] = (f[i][j][1] + c[n][h]) % m;//特判的情况
				if (j == h) f[i][j][0] = (f[i][j][0] + c[n][h]) % m;
				sum = (sum + (f[i][j][0] + f[i][j][1]) % m * fac[2 * n - h] % m) % m;
				//答案累加,到第i+j位依旧合法时会取下一张牌,后面总共会有(2n-i-j)!种情况
			}
		printf("%lld\n", sum);
	}
	return 0;
} 

你可能感兴趣的:(题解,DP)