马尔可夫【MDP】
Alice 和 Bob 正在玩一个 k 轮的石头剪刀布游戏。游戏开始时两位玩家各有恰好 3 张牌每张牌是石 头R、剪刀S或布P。每一轮游戏按照如下方式进行 1. 双方均可看见彼此手中的全部 6 张手牌。 2. Alice 先从她的手牌中选择一张牌打出。 3. Bob 在看到 Alice 打出的牌后选择自己的一张牌打出。 4. 根据标准规则判定胜负石头胜剪刀剪刀胜布布胜石头。相同则为平局。若 Alice 获胜她得 3 分若平局她得 1 分若 Bob 获胜她得 0 分。 5. 双方都将打出的牌丢弃并各自独立地以等概率获得一张新的石头、剪刀或布。 Alice 希望最大化自己的期望总得分Bob 希望最小化 Alice 的期望总得分。 给定游戏轮数 k、Alice 的初始手牌和 Bob 的初始手牌你需要求出双方都采取最优策略时Alice 的最 大期望总得分。 Input 输入的第一行包含一个整数 T (1 ≤ T ≤ 10 5 )表示测试数据组数。对于每组测试数据 第一行包含一个整数 k (1 ≤ k ≤ 10 9 )表示游戏轮数。 第二行包含一个长度为 3 的字符串由字符 R, S, P 组成表示 Alice 的初始手牌。 第三行包含一个长度为 3 的字符串由字符 R, S, P 组成表示 Bob 的初始手牌。 Output 对于每组测试数据输出一行包含一个实数表示 Alice 的最大期望总得分。 如果你的答案的绝对误差或相对误差不超过 10 −6则将被视为正确。形式化地设你的输出为 a标准 答案为 b当且仅当 |a−b| max(1,|b|) ≤ 10 −6 时你的输出会被接受。一、核心思路1. 状态表示去重石头剪刀布中手牌的顺序不影响结果。例如RSP、PSR、SPR是一样的。为了去重我们将手牌排序。Alice 的手牌排序后是一个非降序三元组例如{0, 0, 2}(R, R, P)。这样的组合只有 10 种数学上叫可重复组合(3C33−1​)10。我们将这 10 种组合编号为 0~9。全局状态Alice的状态 10 * Bob的状态。范围 0~99。2. 动态规划定义dp[i][zt]表示还剩下i轮游戏当前状态为zt时Alice 能获得的期望总得分。3. 博弈过程模拟DP转移对于状态zt还剩i轮Alice 出牌她手上有 3 张牌她尝试出第a张位置 0, 1, 2。Bob 出牌他知道 Alice 出了什么。他遍历自己手上的 3 张牌选择一张让 Alice得分最少的牌。弃牌与补牌双方各弃一张然后各自独立随机摸一张R/S/P。这里有 3×39种可能性。状态转移摸牌后手牌变化重新排序进入新状态zt剩余i-1轮。得分计算本轮得分 下一轮期望得分。由于 Bob 是后手且想最小化 Alice 得分内层取minAlice 想最大化得分外层取max。4. 处理巨大的 k线性外推k最大到 10e9不可能算这么多轮。观察发现当 i很大时每多一轮游戏期望得分的增加量趋于稳定就像物理中的匀速运动。我们计算前 1000 轮。计算平均每轮增加的分值 d通过计算第 1000 轮与第 999 轮的差值平均得到。对于 k1000使用公式dp[1000][zt](k−1000)×d。二、带详细注释的代码#include bits/stdc.h using namespace std; #define ll long long #define ull unsigned long long #define ld long double // 使用长双精度防止精度丢失 #define endl \n // 计时器用于调试性能 chrono::_V2::system_clock::time_point bg_clock,en_clock; const int N 1e3 10; // DP数组第一维大小预计算1000轮 ld dp[N][100]; // dp[i][zt]: 剩余i轮状态为zt时的期望得分 // 存储所有10种手牌组合排序后的 std::vectorarrayll, 3 card; // 映射手牌组合 - 索引 (0~9) maparrayll, 3, ll mp; ld d 0; // 稳态下平均每轮的得分增量 /** * brief 判断对局得分 * param a Alice出的牌 (0:R, 1:S, 2:P) * param b Bob出的牌 * return Alice的得分 (0, 1, or 3) */ ll check(int a, int b) { if (a b) { return 1; // 平局 } // (01)%31 (R beats S) // (11)%32 (S beats P) // (21)%30 (P beats R) if ((a 1) % 3 b) { return 3; // Alice赢 } return 0; // Alice输 } /** * brief 初始化函数生成状态空间并计算DP表 */ void init() { ll cnt 0; // 1. 生成所有非降序三元组共10种 // 例如 {0,0,0}, {0,0,1}, {0,1,1}...{2,2,2} for (int i 0; i 3; i) { for (int j i; j 3; j) { for (int k j; k 3; k) { card.push_back({i, j, k}); mp[{i, j, k}] cnt; cnt; } } } // 2. 动态规划计算 // i 代表剩余的轮数 for (int i 1; i N; i) { // 遍历所有100个状态 (Alice 10种 * Bob 10种) for (int zt 0; zt 100; zt) { ld resa -1e18; // Alice能获得的最大期望得分 // Alice尝试打出她手上的第a张牌 (0, 1, or 2) for (int a 0; a 3; a) { ld resb 1e18; // Bob操作后的结果他要最小化这个值 // Bob尝试打出他手上的第b张牌 for (int b 0; b 3; b) { ld sum 0; // 枚举双方随机补充的牌 (na: new Alice, nb: new Bob) for (int na 0; na 3; na) { for (int nb 0; nb 3; nb) { // 解析当前状态 zt // zt % 10 是 Alice 的状态索引, zt / 10 是 Bob 的状态索引 ll za zt % 10; ll zb zt / 10; // 获取当前手牌注意这里是拷贝因为要修改 auto cuna card[za]; // Alice current hand auto cunb card[zb]; // Bob current hand // Alice打出第a张牌并用新牌na填充该位置 cuna[a] na; // Bob打出第b张牌并用新牌nb填充该位置 cunb[b] nb; // 手牌重新排序因为顺序不影响后续状态 sort(cuna.begin(), cuna.end()); sort(cunb.begin(), cunb.end()); // 累加下一轮的期望得分 本轮即时得分 // dp[i-1][...]: 剩下 i-1 轮的期望 // check(...): 本轮得分 sum dp[i - 1][mp[cuna] 10 * mp[cunb]] check(card[za][a], card[zb][b]); } } // Bob选择让他付出代价最小的出牌即最小化sum resb min(resb, sum); } // Alice选择让她获益最大的出牌 // 注意resb 是9种情况的总和除以9才是期望值 resa max(resa, resb / 9.0); } dp[i][zt] resa; } } // 3. 计算稳态平均每轮收益 d // 假设当轮数很大时dp[i] - dp[i-1] 趋近于常数 d for (int i 0; i 100; i) { // 取第1000轮和第999轮的差值作为增量估计 d (dp[1000][i] - dp[999][i]); } d / 100; // 对所有100个状态取平均得到稳定的d } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); bg_clock chrono::high_resolution_clock::now(); cout fixed setprecision(15); // 设置高精度输出 int T 1; cin T; init(); // 一次性预处理所有数据 while (T--) { ll k; cin k; arrayll, 3 ca, cb; // current Alice/Bob hand string sa, sb; cin sa sb; // 将输入的字符转换为数字 (0:R, 1:S, 2:P) for (int i 0; i 3; i) { if (sa[i] R) ca[i] 0; if (sa[i] S) ca[i] 1; if (sa[i] P) ca[i] 2; if (sb[i] R) cb[i] 0; if (sb[i] S) cb[i] 1; if (sb[i] P) cb[i] 2; } // 排序匹配状态定义 sort(ca.begin(), ca.end()); sort(cb.begin(), cb.end()); // 计算当前状态编码 ll zt mp[ca] 10 * mp[cb]; if (k N) { // 如果k小于预计算的轮数直接查表 cout dp[k][zt] endl; } else { // 否则利用线性外推公式计算 // dp[1000][zt]: 前1000轮的得分 // (k-1000)*d: 剩余轮数按平均速度获得的得分 cout dp[1000][zt] (k - 1000) * d endl; } } en_clock chrono::high_resolution_clock::now(); auto duration_clock chrono::duration_castchrono::microseconds(en_clock - bg_clock); ld duration_count duration_clock.count() * 0.001; // cerr Time: duration_count ms endl; // 输出运行时间调试用 return 0; }三、这段代码为什么能过精度控制使用了long double和setprecision(15)远超题目要求的1e-6。状态压缩100个状态使得DP可行。外推合理性由于每轮结束后的随机补牌状态转移是无记忆的马尔可夫性。当轮数趋于无穷平均收益确实会收敛到一个固定值。用第1000轮的数据外推误差极小。博弈逻辑正确严格遵循了“Alice最大化Bob最小化”的零和博弈规则。原理讲解一、马尔可夫性一句话定义给定一个过程如果下一时刻的状态只取决于当前状态跟更早的历史无关这个过程就满足马尔可夫性。形式化写P(St1​∣St​,St−1​,…,S0​)P(St1​∣St​)回到这题当前状态 (Alice手牌, Bob手牌)下一轮手牌只由当前手牌 这轮谁出啥 随机补牌决定前 5 轮是怎么打到这个状态的完全不影响下一轮所以这是个马尔可夫过程。状态只有 100 个就叫有限状态马尔可夫链。二、马尔可夫链的几个性格一条链长什么样主要看三个属性不可约Irreducible从任意一个状态出发迟早能走到任意另一个状态。这题双方每轮都要弃牌随机补牌哪怕现在 Alice 全是 R、Bob 全是 P几轮之内总能变到任何其他组合因为补牌是独立的总有概率摸到想要的。则这条链是不可约的。非周期Aperiodic不存在一个强制的每隔几轮才回来的规律。这题有可能下一轮就回到同一个手牌组合运气好补回来一样的也可能隔几轮才回来。周期可以是 1。这条链是非周期的。遍历Ergodic 不可约 非周期 有限状态三条都占 →你这条链是遍历的。遍历——保证下面所有稳定的结论都成立。三、平稳分布 π最关键的结论遍历链有一个超级重要的定理存在唯一的分布 π(π0​,π1​,…,π99​)满足πPπ并且不管从哪个状态出发玩很多轮之后状态分布都会收敛到 π跟起点无关。π叫平稳分布平稳的意思是如果一开始按 π发手牌那之后每一轮的手牌分布都永远是 π。四、平均报酬定理代码外推题每轮有个即时奖励​ ri​在状态 i下 Alice 这轮的期望得分Bob 已经最优应对了。遍历链 常驻奖励有个定理叫平均报酬定理k→∞lim​kVk​(目i)​ρ:j0∑99​πj​rj​ρ就是长期平均每轮得分是个常数跟起点 i无关。更精细一点值函数可以拆成Vk​(i)kρhi​o(1)其中 hi​叫偏差bias只跟起点有关是常数。o(1)那项是残余随 k指数衰减。五、把代码里的变量对号入座数学符号你代码里的东西含义S100dp[][100]第二维状态数Pji​9 种补牌等概率转移概率ri​check(...) 下一轮dp里的即时部分单轮奖励π--平稳分布ρd平均每轮得分hi​dp[1000][i] - 1000*d的隐式估计偏差d (dp[1000][i] - dp[999][i]); // 对所有i平均 d / 100;数学上dp[1000][i]−dp[999][i]ρ(hi​−hi​)残留≈ρ再对所有 i平均一把残留更小 →用数值差分估 ρ。然后外推dp[1000][zt] (k-1000)*d // 1000*ρ h_zt 残 (k-1000)*ρ // k*ρ h_zt 残正好是 Vk​(zt)的近似。六、这套知识在竞赛里还用在哪马尔可夫链 平稳分布这套在算法竞赛里是一类题的通解题型例子随机游走求长期比例蚂蚁在图上爬问某条边被走的概率期望 DP 巨大轮数这题就是吸收态问题gamblers ruin、毒药糖果随机过程博弈Bob 后手通用套路建模状态证明/确认是马尔可夫链判遍历性​ → 有平稳分布 π小 k 直接 DP大 k要么像本题这样数值外推好写够用要么解 πPπ 得到精确 ρ再配偏差 h更稳适合 k 到 1e18 的题七、这题如果用正统 MDP 解法现在的代码 数值迭代 线性外推是工程师解法。正统 MDP / 马尔可夫报酬链解法会多两步策略迭代或值迭代得到最优策略下固定的转移 P∗和奖励 r∗解πP∗π→ 平稳分布ρπr∗(I−P∗1π)hr∗−ρ1→ 偏差答案 kρhi​−(P∗kh)i​大 k 时最后一项丢掉好处ρ 和 h 都是解析精确值不用赌1000 轮够不够稳。

相关新闻

从AI乱编到出版级输出:一位童书编辑+AI训练师双身份者的12小时实战复盘——如何用ChatGPT批量产出获凯迪克奖风格故事脚本

从AI乱编到出版级输出:一位童书编辑+AI训练师双身份者的12小时实战复盘——如何用ChatGPT批量产出获凯迪克奖风格故事脚本

更多请点击: https://intelliparadigm.com 第一章:从AI幻觉到出版级叙事的范式跃迁 当大语言模型生成“华盛顿特区位于加拿大”这类事实性错误时,我们遭遇的并非偶然失误,而是底层概率建模与真实世界语义锚定之间的结构性断裂。出…

2026/10/22 2:38:02 阅读更多 →
紧急通知:网信办新规实施倒计时72小时!你的AI评论系统是否通过“三审一校”兼容性验证?

紧急通知:网信办新规实施倒计时72小时!你的AI评论系统是否通过“三审一校”兼容性验证?

更多请点击: https://intelliparadigm.com 第一章:紧急通知:网信办新规实施倒计时72小时!你的AI评论系统是否通过“三审一校”兼容性验证? 距离《生成式人工智能服务安全评估办法》配套实施细则正式生效仅剩72小时。网…

2026/10/22 11:57:42 阅读更多 →
教务排课中的多约束冲突解决:当40条特殊需求同时砸过来,算法如何破局?

教务排课中的多约束冲突解决:当40条特殊需求同时砸过来,算法如何破局?

排课不是简单的表格填空,而是一场约束满足问题与人性博弈的复合战役。本文从真实教务场景出发,探讨多优先级约束冲突下的排课优化思路。一、问题缘起:一张课表背后的人性博弈 如果你问一位教务老师,排课最难的是什么?答…

2026/10/22 11:57:32 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →