B - Auspiciousness
题目大意
你一个牌堆,其中有 2 n 2n 2n 张牌,排面为 1 , 2 , … , 2 n 1,2,…,2n 1,2,…,2n,需要进行如下操作:
-
从牌堆顶取走一张牌
-
如果这张牌大于 n n n 则猜测下一张小于这张牌,否则猜测下一张大于这张牌
-
取走下一张牌判断与猜测情况是否相同,相同则回到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 <bits/stdc++.h>
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;
}