2023牛客暑假多校第三场(补题向题解:B)

挺可惜的,B题破题点思路已经想到了,但手忙脚乱最终没写出来,又因为赛程紧张也没来得及及时补,最近才开始着手补这些落下的题。

B Auspiciousness(组合数学 + DP)

题意

随机排序的 1 ∼ 2 n 1\sim 2n 12n 2 n 2n 2n 张卡牌,一开始获得第一张牌,接着摸下一张卡牌并按给定的策略猜测下一张卡牌比当前卡牌大/小,猜对可以继续进行,否则游戏结束,(猜错的那张卡牌也算摸到手上了),问所有可能的排列能摸到的卡牌数量之和是多少。
猜牌的策略:若当前卡牌 a i ≤ n a_i \leq n ain,则猜下一张卡牌更大,否则猜下一张卡牌更小。

思路

链接:B Auspiciousness
关键的破题点: 如果猜对,那么说明肯定是如下情况,设猜对牌序列 a a a 长度为 k k k
若当前 a i ≤ n a_i \leq n ain a i + 1 > n / a i + 1 < a i a_{i + 1} > n / a_{i + 1} < a_i ai+1>n/ai+1<ai
若当前 a i > n a_i > n ai>n a i + 1 ≤ n / a i + 1 > a i a_{i + 1} \leq n / a_{i + 1} > a_i ai+1n/ai+1>ai
即一定是 ≤ n \leq n n 的牌升序排列 与 > n >n >n 的牌降序排列交错。
k < 2 n k < 2n k<2n 则最后还可以摸到一张猜错的牌。

修改一下题目意思,只有当猜错或者猜对所有牌时才能拿走牌,对结果没有影响。
考虑dp方程 f [ i ] [ j ] [ 2 ] : f[i][j][2]: f[i][j][2]: 总共摸 i i i 张牌,其中 ≤ n \leq n n 的牌还剩下 j j j 张,且最后一段序列摸的牌是升序的 ≤ n \leq n n的/ 降序的 > n >n >n 的都猜对的牌(对猜错的情况不做统计,只是计入答案)。

具体转移看代码,有详细注释。

#include 
using namespace std;

#define ll long long
const int N =  610;

int n, m, p;

ll f[N][N][2]; // 打出i张牌,<= n 的牌还有j张, 且当前一段选小于等于n/大于n的合法(没猜错)方案数
ll C[N][N], fac[N]; // 组合数学 / 阶乘
void init(){
	fac[0] = 1;
	for(int i = 1; i <= m; i ++) fac[i] = fac[i - 1] * i % p;
    for(int i = 0; i <= m; i ++){
        for(int j = 0; j <= i; j ++){
            if(i == j || j == 0) C[i][j] = 1;
            else C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % p;
        }
    }
}

void add(ll& a, ll b){
    a = (a + b) % p;
}

void solve(){
    cin >> n >> p;
    m = 2 * n;

    init();
    ll ans = 0;
    memset(f, 0, sizeof f);
    f[0][n][0] = f[0][n][1] = 1;
    for(int i = 1; i <= m; i ++) { // 枚举当前打出的牌的总数
        for(int j = 0; j < i; j ++){ // 枚举上一次打出的牌的总数
            // 本次一口气打出 i - j 张升序或降序的牌
            for(int k = 0; k <= n; k ++){ // 枚举上一次打完牌,还剩多少小于等于n的牌
                ll sum = n - (j - (n - k)), need = i - j; // sum:大于n的牌还剩下多少,当前需要打出的牌的数量
                
                if(f[j][k][0] && sum >= need){
                    add(f[i][k][1], f[j][k][0] * C[sum][need] % p); // 没有猜错,可以接着猜
                    if(sum >= need + 1){ // 猜错一张牌,可以拿走的牌的数量
                    	ll res = f[j][k][0] * C[sum][need + 1] % p * need % p * fac[m - i - 1] % p * (i + 1) % p;
                        //转移的状态  * 从sum张牌摸 need + 1 张降序的牌 * (任选一张非最大的牌放到最后当做猜错的牌) * 后续对结果无影响的牌排列的可能性 * 摸的牌的数量(i + 1)
                        ans = (ans + res) % p;
                    }
                }
                if(f[j][k][1] && k >= need){ // 同理
                    add(f[i][k - need][0], f[j][k][1] * C[k][need] % p);
                    if(k >= need + 1){
                    	ll res = f[j][k][1] * C[k][need + 1] % p * need % p * fac[m - i - 1] % p * (i + 1) % p;
                        ans = (ans + res) % p;
                    }
                }
            }
        }
    }
    ans = (ans + ((f[m][0][0] + f[m][0][1]) % p) * m % p) % p; // 全部猜对
    cout << ans << "\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int t;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

你可能感兴趣的:(组合数学和概率论,牛客寒假暑假训练营题解,DP,专栏,c++,算法)