Concentration

Concentration

1. 问题重述

\(2N\) 张牌,对于每个 \(i = 1..N\),数字 \(A_i\) 恰好出现两次(所有 \(A_i\) 互异)。牌随机排列,背面朝上。
游戏参数:初始生命 \(L\),分数 0。
每回合操作:

  • 翻第一张牌(知道数字 \(X\)),再翻第二张(知道数字 \(Y\))。
  • \(X = Y\),配对成功,移除这两张,分数增加 \(X\)
  • \(X \ne Y\),翻回背面,生命 \(-1\)
    游戏结束条件:生命为 0 或桌上无牌。

高桥君知道所有数字,但不知道排列,翻过的牌会记住。
求采取最优策略时,最终得分的期望值。


2. 关键简化

2.1 最优策略的结构

结论 1:存在一种最优策略,其行为只依赖于“已翻开的牌的信息”,而与具体数字大小无关。
理由:因为配对成功获得分数是数字本身,但数字的顺序不影响配对成功的概率(所有牌等概率随机排列)。最优策略在“最大化配对数量”上等同于“最大化期望对数”,最终期望得分 = 期望配对数量 × 数字平均值。

结论 2:最优策略中,当桌上存在已知完整配对(即某数字的两张都已翻开)时,应 立即 将它们配对(下一回合直接取这两张)。
理由:配对不消耗生命,稳赚 \(1\) 对,且不损失信息,是最优的。

结论 3:在没有已知配对时,第一张牌总是选择一张未知牌(即尚未翻过的牌),第二张也选择未知牌(除非第一张恰好与某张已知单张配对,此时第二张应选该已知单张)。
理由:翻已知牌不会带来新信息,且无法在当前回合配对;翻未知牌能增加信息,且不劣于翻已知牌。

因此,我们只需考虑两类数字:

  • 完全未知的数字:两张都未翻过,记种类数为 \(c_0\)
  • 已知一张的数字:已翻开一张,另一张未知,记种类数为 \(c_1\)

其余状态(如已知两张但未配对)可视为立即配对,不会出现在最优策略的 DP 转移中。


3. 状态定义

\(dp[l][c_0][c_1]\) 表示:

  • 当前剩余生命为 \(l\)
  • 桌面上有 \(c_0\) 种完全未知的数字(每种有 2 张牌)。
  • \(c_1\) 种已知一张的数字(每种有 1 张未知牌,1 张已知牌)。
  • 当前没有已知完整配对(若有,应已提前配对)。

在此状态下,从当前开始到游戏结束,采取最优策略时,期望还能配成的对数

边界条件

  • \(l = 0\)\((c_0 = 0 \text{ 且 } c_1 = 0)\),则 \(dp = 0\)
  • \(c_0 = 0\)(只剩已知单张),则 \(dp[l][0][c_1] = c_1\),因为每对都已知,可连续配对,不消耗生命。

4. 转移推导

\(U = 2c_0 + c_1\) 为当前未知牌(背面朝上未翻过)的总数。
第一张必定从这些未知牌中选择。

情况 1:第一张来自某个“已知一张”数字的未知牌

  • 概率:\(\dfrac{c_1}{U}\)
  • 翻开后,我们立即知道它的配对就是那张已知牌(因为已知数字),所以第二张选那张已知牌,配对成功。
  • 配对成功,得 1 对,状态变为 \((l,\ c_0,\ c_1 - 1)\)
  • 期望贡献:\(\dfrac{c_1}{U} \cdot \bigl(1 + dp[l][c_0][c_1 - 1]\bigr)\)

情况 2:第一张来自某个“完全未知”数字

  • 概率:\(\dfrac{2c_0}{U}\)
  • 翻开后,这张牌属于某个完全未知数字,现在该数字变成“已知一张”(因为另一张未翻)。
  • 第二张仍然从剩余未知牌(数量为 \(U - 1\))中选择。

2a. 第二张是第一张的配对牌

  • 概率:\(\dfrac{1}{U - 1}\)
  • 配对成功,得 1 对,该数字配对完毕,状态变为 \((l,\ c_0 - 1,\ c_1)\)
  • 贡献:\(\dfrac{2c_0}{U} \cdot \dfrac{1}{U - 1} \cdot \bigl(1 + dp[l][c_0 - 1][c_1]\bigr)\)

2b. 第二张来自另一个完全未知数字的一张

  • 概率:\(\dfrac{2(c_0 - 1)}{U - 1}\)(需 \(c_0 \ge 2\))。
  • 配对失败,生命 \(-1\)。两张牌分别属于两个不同的完全未知数字,它们都变成了“已知一张”,所以 \(c_0\) 减少 2(这两个数字不再是完全未知),\(c_1\) 增加 2(两个新的已知单张)。
  • 状态变为 \((l - 1,\ c_0 - 2,\ c_1 + 2)\)
  • 贡献:\(\dfrac{2c_0}{U} \cdot \dfrac{2(c_0 - 1)}{U - 1} \cdot dp[l - 1][c_0 - 2][c_1 + 2]\)(需 \(l > 1\))。

2c. 第二张来自某个“已知一张”数字的未知牌

  • 概率:\(\dfrac{c_1}{U - 1}\)(需 \(c_1 > 0\))。
  • 配对失败,生命 \(-1\)。第二张与它对应的已知牌构成了一对完整配对,但我们没有在本回合配对(因为规则要求两次翻开后若不同,则翻回)。然而,因为现在这两张都已翻开且我们知道它们是一对,在下一回合我们可以立即选择它们配对。因此,虽然本回合失败,但下一回合会获得 1 对,且不额外消耗生命(因为下一回合第一张选已知牌)。
    同时,第一张牌(来自完全未知数字)变成了新的“已知一张”,所以 \(c_0\) 减少 1(该数字不再是完全未知),而第二张所在数字已形成完整配对并被移除,所以 \(c_1\) 的数量不变(新增一个已知单张,抵消了被移除的那个)。
  • 状态变为 \((l - 1,\ c_0 - 1,\ c_1)\),且下一回合必然获得 1 对,所以当前贡献可视为“\(1 + dp[\text{下一状态}]\)”。
  • 贡献:\(\dfrac{2c_0}{U} \cdot \dfrac{c_1}{U - 1} \cdot \bigl(1 + dp[l - 1][c_0 - 1][c_1]\bigr)\)(需 \(l > 1\))。

注意:以上所有概率均基于“第二张在剩余未知牌中选择”的假设,分母为 \(U - 1\),而不是总牌数 \(2c_0 + 2c_1\)。这是由最优策略决定的,因为我们从不主动选择已知牌作为第二张(除非第一张配对,但那属于情况 1)。


5. 最终答案

\(X = dp[L][N][0]\)(初始状态:生命 L,所有 N 个数字完全未知)。
数字平均值为 \(\bar{A} = \dfrac{1}{N}\sum_{i=1}^N A_i\)

因为每种数字被配对的概率相同,期望得分 = 期望配对数量 × 数字平均值,即

\[\text{Answer} = X \cdot \bar{A}. \]


#include <bits/stdc++.h>
using namespace std;
using ll = long long;

double dp[201][201][201];
bool vis[201][201][201];

double dfs(int l, int c0, int c1) {
    if(l == 0 || (c0 == 0 && c1 == 0)) return 0.0;
    if(vis[l][c0][c1]) return dp[l][c0][c1];
    vis[l][c0][c1] = true;

    int t = 2 * c0 + 2 * c1;
    int u = 2 * c0 + c1;    
    
    if(c1 > 0) {
        double p = (double)c1 / u;
        dp[l][c0][c1] += p * (1.0 + dfs(l, c0, c1 - 1));
    }
    if(c0 > 0) {
        double p1 = (double)2.0 * c0 / u;
        double p2 = (double)1.0 / (u - 1);
        dp[l][c0][c1] += p1 * p2 * (1.0 + dfs(l, c0 - 1, c1));
        if(c0 - 1 > 0 && l > 1) {
            p2 = (double)2.0 * (c0 - 1) / (u - 1);
            dp[l][c0][c1] += p1 * p2 * dfs(l - 1, c0 - 2, c1 + 2);
        }
        if(c1 > 0 && l > 1) {
            p2 = (double)c1 / (u - 1);
            dp[l][c0][c1] += p1 * p2 * (1.0 + dfs(l - 1, c0 - 1, c1));
        }
    }
    return dp[l][c0][c1];
}

void solve() {
    int n, l; cin >> n >> l;
    ll tol = 0;
    for(int i = 0; i < n; i ++) {int a; cin >> a; tol += a;}
    double aver = (double)tol / n;

    double ans = aver * dfs(l, n, 0);
    cout <<  fixed << setprecision(10) << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int t = 1; //cin >> t;
    while(t --) solve();
    return 0;
}
posted @ 2026-08-28 15:16  thenrify  阅读(3)  评论(0)    收藏  举报