登录—专业IT笔试面试备考平台_牛客网
题目大意:有一个含有2n张牌的牌堆,当手牌为空时,
从牌堆顶抽一张牌,然后猜牌堆顶的牌和手牌顶的牌的大小关系,并抽牌,如果猜对了继续循环,否则游戏直接结束,猜牌的策略是如果手牌顶的牌<=n,那么就猜下一张牌更大,反之就猜更小,问在总共2n!种牌堆的排列情况中,一共能抽到几张牌
1<=n<=300
思路:我们称所有<=n的牌为小牌,>n的为大牌,我们发现游戏结束的情况只有两种,一种是先抽到一张小牌,然后又抽到一张比他更小的牌,令一种是抽到一张大牌,然后又抽到一张比他更大的牌。那么我们定义dp[i][x][y][k],i为0/1分别表示上一张牌是小牌/大牌,x/y分别表示当前牌堆剩余x张小牌和y张大牌,k表示我们当前抽到的牌是小牌堆里第k小的或大牌堆里第k大的,首先我们对第四维求前缀和,也就是dp[0/1][n][n][i]=i,然后我们从牌堆满也就是x=n,y=n到牌堆空x=0,y=0的情况枚举转移:
首先讨论当前抽到的牌如果是小牌,那么继续讨论如果上一张牌抽到的也是小牌那么当前牌的取值只能是[1,k]当前牌大小-1]因为我们设的k是第k小,所以dp[0][x][y][k]+=dp[0][x+1][y][x+1]-dp[0][x+1][y][k],如果上一张抽到的是大牌,那么任意的大牌都可以,dp[0][x][y][k]+=dp[1][x][y+1][y+1]。然后维护第四维前缀和dp[0][x][y][i]+=dp[0][x][y][i-1],i在[1,x]内
然后讨论当前抽到的牌是大牌,和上面几乎一样,详见代码,最后我们在补上没考虑到的部分,也就是当出现 游戏提前结束的情况是,导致游戏结束的那张牌也要记在答案里,可以用总的情况数减去所有牌抽完的情况数,也就是2n!-取走最后一张小牌的情况dp[0][1][0][1]-取走一张大牌的情况dp[1][0][1][1]
//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 305;
pair<ll,int> a[N];
ll fac[N << 1];
ll dp[2][2][N][N];
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--){ll n, MOD;cin >> n >> MOD;fac[0] = 1;for (int i = 1; i <= 2*n; i++){//预处理阶乘fac[i] = fac[i - 1] * i%MOD;}for (int i = 0; i <= 1; i++){for (int j = 0; j <= n; j++){for (int k = 0; k <= n; k++){dp[0][i][j][k] = dp[1][i][j][k] = 0;//清零}}}ll ans = fac[2*n];//每种情况都至少能抽到一张for (int i = 1; i <= n; i++){//dp[0][0][n][i] = dp[1][0][n][i] = i%MOD;//在牌堆满时预处理前缀和}int op = 0;//因为二维只与x和x+1有关,所以用滚动数组优化成0/1for (int x = n; x >= 0; x--){for (int y = n; y >= 0; y--){if (x + y == 2 * n)continue;//牌堆满时已经预处理过了for (int k = 1; k <= x; k++){if (x != n){dp[0][op][y][k] = (dp[0][op][y][k] + (dp[0][op^1][y][x + 1] - dp[0][op^1][y][k] + MOD) % MOD) % MOD;//当前抽小牌,上一张也是小牌}if (y != n)dp[0][op][y][k] = (dp[0][op][y][k] + dp[1][op][y + 1][y + 1]) % MOD;//当前抽小牌,上一张是大牌ans = (ans + dp[0][op][y][k] * fac[x + y - 1] % MOD) % MOD;//后面的牌的方案数有(x+y-1)!种}for (int k = 1; k <= x; k++){dp[0][op][y][k] = (dp[0][op][y][k] + dp[0][op][y][k - 1]) % MOD;}//维护前缀和for (int k = 1; k <= y; k++){if (y != n){dp[1][op][y][k] = (dp[1][op][y][k] + (dp[1][op][y + 1][y + 1] - dp[1][op][y + 1][k] + MOD) % MOD) % MOD;//当前抽大牌,上一张也是大牌}if (x != n){//当前抽大牌,上一张是小牌dp[1][op][y][k] = (dp[1][op][y][k] + dp[0][op^1][y][x+1]) % MOD;}ans = (ans + dp[1][op][y][k] * fac[x + y - 1]%MOD)%MOD;}for (int k = 1; k <= y; k++){//维护前缀和dp[1][op][y][k] = (dp[1][op][y][k] + dp[1][op][y][k - 1])%MOD;}}op ^= 1;//滚一下for (int i = 0; i <= n; i++){for (int j = 0; j <= n; j++){//把滚过来的地方清空dp[0][op][i][j] = dp[1][op][i][j] = 0;}}}ans = (ans + (fac[2 * n] - dp[1][op^1][1][1]*2+2*MOD))%MOD;//加上所有抽不完所有牌的情况各一张cout << ans << endl;}return 0;
}