Live2D

Solution -「GLR-R4」芒种

Description

  Link, 懒得概括题意.jpg

Solution

  Subtask 1 (n,m2) 一共只有五种情况, 样例已经给出了三种, 剩下两种自己手玩就行啦!

  Subtask 2 (n,m8) 奇奇怪怪的状压, 或者忘记记忆化的各色搜索应该都能得到这档.

  Subtask 3 (n=m) 观察样例可以发现, 此时也许有 ans=n/2, 特别的, 当 n=m=1 时, ans=1. 严谨的说, 我们可以证明, 当 n=m>1 时, 双方一开始就会和局.

Proof   尝试归纳证明. 假设当 2nn0 时, 先手期望得分不低于后手. 现在, 我们来考虑 n=n0+1 的情况. 此时先手一共只有三种决策:
  1. 选择两张未知牌 (未知牌共 2nm=n=m 张);

  2. 选择一张已知牌 (已知牌共 m 张), 一张未知牌;

  3. 选择两张已知牌.

  对于第一种决策, 因为已知牌中 n 种类型都已经出现, 所以先手不可能拿到相同类型的牌. 此时先手必然结束自己的回合. 而后手可以立马 "捡漏", 将先手翻出的两张牌和已知的两张牌分别配对, 得到 2 分. 接着, 后手变为先手, 面对 nn2 的情况. 根据归纳假设, 此时的先手期望得分不劣于后手, 所以在这种决策下, 原先手的期望得分低于后手.

  对于第二种决策, 类似的, 先手有 1/n 的概率直接拿出一对同类卡牌, 以 (n1)/n 的概率给后手 "打工", 而二者都会递归到 nn1 的情况. 设 nn1 时, 先手期望得分为 x (x(n1)/2), 那么原来先手的期望得分为:

E=1n(1+x)+n1n(n1x)=1n+(n1)2nn2nx1n+(n1)2n(n1)(n2)2n=2+2n24n+2n2+3n22n=n212+1n.

可见 n2 时都有 En2, 也即是原先手期望得分低于后手.

  于是, 先手选择了第三种决策! 第三种决策没有任何效果, 仅仅是 "摆烂" 地过掉自己回合, 轮到对方操作. 此时对方又面临这三种决策的选择, 他又会 "摆烂" 过掉自己的回合 ... 双方都无法让自己的期望得分高于对方, 那么此时双方都会同意和局! 两人得分 n/2, 我们完成了归纳, 也顺带证明了游戏一开始即和局结束.  

  Subtask 4 (T=1) 大概有什么一次只能求出一个答案的算法?

  Subtask 5 来讲正解啦. 在 subtask 3 的证明过程中, 我们已经自然地引入了 "先手得分是否低于后手" 的讨论. 进一步的, 由于双方得分之和一定是 n, 所以我们可以用双方得分之差来刻画先手得分. 令 f(n,m) 表示 n 对牌, m 张已知时, 先手期望得分 后手期望得分的值. 还是来做一做同 subtask 3 证明过程中的三种讨论:

  • 先手选择一张已知牌, 一张未知牌, 此时要求 m1, 有转移:

    f(n,m)max12nm(1+f(n1,m1))m12nm(1+f(n1,m1))2n2m2nmf(n,m+1).

  • 先手选择两张未知牌, 此时要求 2nm2, 有转移:

    f(n,m)maxnm(2nm2)(1+f(n1,m))(m2)(2nm2)(2+f(n2,m2))m(2n2m)(2nm2)(1+f(n1,m))2(nm)(nm1)(2nm2)f(n,m+2).

  • 先手选择两张已知牌, 此时要求 m2. 注意这里不是 f(n,m)maxf(n,m), 如果这种决策是优秀的, 双方会直接和局, 所以有:

    f(n,m)max0.

  边界为 f(0,0)=0. 转移过程有很多细小情况的讨论, 可能会引入非法状态, 但这些非法状态的转移系数必然为 0, 所以不必过分担心. 直接递推或者记忆化搜索, 就能 O(nm) 求出所有 f. 最终答案即为 (n+f(n,m))/2.

Code

/*+Rainybunny+*/

#include <bits/stdc++.h>

#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)

typedef double VType;
// typedef long double VType;

template <typename Tp>
inline void chkmin(Tp& u, const Tp& v) { v < u && (u = v, 0); }
template <typename Tp>
inline void chkmax(Tp& u, const Tp& v) { u < v && (u = v, 0); }
template <typename Tp>
inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; }
template <typename Tp>
inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; }

const int MAXN = 5e3;
bool vis[MAXN + 5][MAXN + 5];
VType f[MAXN + 5][MAXN + 5];

inline VType calc(const int n, const int m) {
    if (n <= 0 || m < 0 || n < m) return 0;
    VType& cur = f[n][m];
    if (vis[n][m]) return cur;
    vis[n][m] = true, cur = -1e100;
    if (m >= 1) {
        chkmax(cur, (1 + calc(n - 1, m - 1)
          - (m - 1) * (1 + calc(n - 1, m - 1))
          - 2 * (n - m) * calc(n, m + 1))
          / (2 * n - m));
    }
    if (2 * n - m > 1) {
        chkmax(cur, 2 * ((n - m) * (1 + calc(n - 1, m))
          - m * (m - 1) / 2 * (2 + calc(n - 2, m - 2))
          - 2 * m * (n - m) * (1 + calc(n - 1, m))
          - 2 * (n - m) * (n - m - 1) * calc(n, m + 2))
          / ((2 * n - m) * (2 * n - m - 1)));
    }
    if (m >= 2) chkmax(cur, 0.);
    return cur;
}

int main() {
    int T, n, m;
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &n, &m);
        printf("%.12f\n", (n + calc(n, m)) / 2);
    }
    return 0;
}

posted @   Rainybunny  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示