1 条题解
-
0
一、问题分析与数学建模
1.1 问题本质
这是一道概率期望 + 贪心策略问题,核心在于理解失落的定义并设计最优的项目安排方案。
问题形式化
状态空间:
- 心情:{高兴, 不高兴}
- 初始状态:不高兴(第0天之后)
项目:
- 第 个项目:不高兴概率 ,高兴概率
- 可用次数:
失落定义:
$$\text{失落} \Leftrightarrow \text{昨天高兴} \land \text{今天不高兴} $$目标:最小化 天内失落的期望次数。
1.2 核心数学推导
定理 1:失落期望的计算公式
命题:某一天发生失落的期望等于:
$$E[\text{失落}] = P(\text{昨天高兴}) \times P(\text{今天不高兴}) $$证明: 失落是 0-1 随机变量:
$$E[\text{失落}] = 1 \cdot P(\text{失落}) + 0 \cdot P(\text{不失落}) = P(\text{失落}) $$由于每天的项目结果相互独立,失落需要两个事件同时发生:
$$P(\text{失落}) = P(\text{昨天高兴}) \times P(\text{今天不高兴}) $$
定理 2:连续使用同一项目
命题:连续使用概率为 的项目 次,从不高兴概率为 的状态开始,总失落期望为:
$$E_{\text{total}} = (1-q) \cdot p + (m-1) \cdot (1-p) \cdot p $$证明:
第 1 次:
- 昨天不高兴概率:,高兴概率:
- 今天不高兴概率:
- 失落期望:
第 2 到第 次:
- 昨天的状态就是这个项目的结果(概率 )
- 昨天高兴概率:
- 今天不高兴概率:
- 每次失落期望:
- 共 次
总期望:
$$E_{\text{total}} = (1-q) \cdot p + (m-1) \cdot (1-p) \cdot p $$
定理 3:贪心策略的最优性
问题:在某一步,当前"接口"状态有两个选择:
- 选择概率为 的项目(左端,概率较大)
- 选择概率为 的项目(右端,概率较小)
假设之前的状态使得:
- 如果选左端,前一步的不高兴概率为
- 如果选右端,前一步的不高兴概率为
下一步无论如何都要使用概率为 的项目。
分析:
选左端的期望:
选右端的期望:
差值:
$$\begin{aligned} E_l - E_r &= (1-q_l)p_l - (1-q_r)p_r + (p_r - p_l)p_{next} \\ &= \ldots \text{(复杂的表达式)} \end{aligned}$$虽然完整推导复杂,但代码采用的启发式策略是:
贪心准则:当 时,选择概率较大的(左端);否则选择概率较小的(右端)。
1.3 边界优化策略
关键观察:
第 1 天:
- 初始状态:不高兴
- 无论选什么,都不会失落(因为昨天不高兴)
- 策略:选择概率最小的项目(让她最可能高兴),为后续减少失落
第 天:
- 这是最后一天,之后没有失落了
- 策略:选择概率最大的项目(反正后面没有影响了)
中间天数:根据贪心策略动态选择。
二、算法设计
2.1 整体策略
预处理:
- 过滤掉次数为 0 的项目
- 按不高兴概率降序排序
- 左指针 (最大概率),右指针 (最小概率)
初始化:
- 第 1 天:使用右端项目(最小概率)
- 第 天:使用左端项目(最大概率)
- 剩余 天需要安排
贪心填充:
- 维护左端状态 和右端状态
- 根据 的值决定选择哪一端
- 批量处理相同概率的项目
最后连接: 计算从 到 的失落期望。
2.2 时间复杂度
排序:
主循环:
- 最坏情况:
- 优化后(批量处理):接近
总复杂度:
三、代码模块详解
模块 1:输入与预处理
void Main() { scanf("%d %d", &n, &k); std::vector<std::pair<long double, int>> a(n); for (int i = 0, x, y; i < n; i++) { scanf("%d/%d %d", &x, &y, &a[i].second); a[i].first = 1.0 * x / y; // 计算不高兴概率 // 过滤次数为 0 的项目 if (!a[i].second) n--, i--; } a.resize(n);数据结构:
a[i].first:第 个项目的不高兴概率a[i].second:剩余可用次数
优化点:
- 实时过滤无效项目,减少后续判断
- 使用
long double保证精度
模块 2:特殊情况
if (k == 1) { puts("0.000000"); return; }数学依据:
- 只有 1 天,初始状态不高兴
- 失落 = 昨天高兴 今天不高兴
- 没有"昨天高兴"的可能 → 期望 = 0
模块 3:排序与初始化
std::sort(all(a), std::greater<>()); int l = 0, r = n - 1; long double nowl = a[l].first, nowr = a[r].first; long double ans = 0; k -= 2; a[l].second -= 1, a[r].second -= 1;排序:
- 降序: 概率最大, 概率最小
边界处理:
- 第 1 天:右端(概率最小) →
- 第 天:左端(概率最大) →
- 各预留一次,剩余 天
状态含义:
- :左端当前的不高兴概率(用于后续决策)
- :右端当前的不高兴概率(用于后续决策)
模块 4:主循环 - 贪心选择
while (k) { // 跳过已用完的项目 while (!a[l].second) l++; while (!a[r].second) r--; int delta; if (nowl + nowr >= 1.0) { // 选择左端(概率大) // ... } else { // 选择右端(概率小) // ... } }决策准则:
- 当 时:选择左端
- 当 时:选择右端
数学直觉:
- 意味着两个状态都比较"坏"
- 此时选择更坏的(左端),利用定理3的贪心策略
情况 1:选择左端
if (nowl + nowr >= 1.0) { // 选择左端 if (a[l].first == nowl) delta = std::min(k, a[l].second); else delta = 1; k -= delta; a[l].second -= delta; ans = ans + (1 - nowl) * a[l].first + (delta - 1) * (1 - a[l].first) * a[l].first; nowl = a[l].first; }批量优化:
- 如果新项目概率 = 当前状态:可连续使用 次
- 否则:只使用 1 次,更新状态
期望计算(根据定理 2): 设 ,使用 次。
- 第 1 次:
- 后续 次: 每次
总期望:
$$(1 - \text{nowl}) \cdot p_l + (\delta - 1) \cdot (1 - p_l) \cdot p_l $$状态更新:
nowl = a[l].first;
情况 2:选择右端
else { // 选择右端 if (a[r].first == nowr) delta = std::min(k, a[r].second); else delta = 1; k -= delta; a[r].second -= delta; ans = ans + (1 - a[r].first) * nowr + (delta - 1) * (1 - a[r].first) * a[r].first; nowr = a[r].first; }对称逻辑:
设 ,使用 次。
期望计算:
注意:这里的公式略有不同!
(1 - a[r].first) * nowr这里 的含义:
- 这是算法的巧妙之处
- 由于双端填充的对称性,期望计算方式有所调整
根据定理 2 的对称形式,总期望为:
$$(1 - p_r) \cdot \text{nowr} + (\delta - 1) \cdot (1 - p_r) \cdot p_r $$状态更新:
nowr = a[r].first;
模块 5:最后连接
ans = ans + (1 - nowl) * nowr; printf("%.6Lf\n", ans);最后一步:
- 从 状态到 状态
- 失落期望:
含义:
- :倒数第二步的不高兴概率
- :倒数第二步的高兴概率
- :最后一步的不高兴概率
- 期望:
四、样例验证
样例 1:,
项目:不高兴概率 = 0(必定高兴)
分析:
- 第 1 天:必定高兴
- 第 2 天:必定高兴
- 从不高兴 → 高兴:无失落
- 从高兴 → 高兴:无失落
- 答案:0
样例 2:,
项目:不高兴概率 = 1(必定不高兴)
分析:
- 第 1 天:必定不高兴
- 第 2 天:必定不高兴
- 从不高兴 → 不高兴:无失落
- 答案:0
样例 3:,
项目:不高兴概率 = 0.5
执行过程:
- 排序后:
- (只有一个项目)
- → ,跳过循环
- 最后连接:
数学验证:
所有可能的情况(等概率):
- 不高兴 → 不高兴:无失落
- 不高兴 → 高兴:无失落
- 高兴 → 不高兴:失落 1 次
- 高兴 → 高兴:无失落
但初始状态是不高兴,所以:
- 第1天不高兴,第2天不高兴:概率 ,失落 0 次
- 第1天不高兴,第2天高兴:概率 ,失落 0 次
- 第1天高兴,第2天不高兴:概率 ,失落 1 次
- 第1天高兴,第2天高兴:概率 ,失落 0 次
期望:
$$E = 0.25 \times 0 + 0.25 \times 0 + 0.25 \times 1 + 0.25 \times 0 = 0.25 $$
五、算法正确性分析
5.1 边界策略的正确性
第 1 天选最小概率:
- 初始不高兴,第 1 天无失落
- 选最小概率让她最可能高兴,为后续减少失落
第 天选最大概率:
- 最后一天后没有失落
- "浪费"一个高概率项目,不影响总期望
5.2 贪心策略的有效性
虽然严格证明复杂,但直觉上:
- 当两端状态都"坏"时(),选择更坏的
- 当至少有一端状态"好"时,选择更好的
- 这符合期望最小化的目标
六、复杂度总结
时间复杂度
- 排序:
- 主循环:(批量优化后)
- 总复杂度:
空间复杂度
- (存储项目信息)
七、易错点与注意事项
7.1 易错点 1:精度问题
问题:概率计算涉及除法,需要高精度。
// ✅ 正确 a[i].first = 1.0 * x / y; // long double // ❌ 错误 a[i].first = x / y; // 整数除法
7.2 易错点 2:边界条件
的特判:
- 必须特判,否则 会出错
项目用完的处理:
while (!a[l].second) l++; while (!a[r].second) r--;必须在每次循环开始时检查。
7.3 易错点 3:公式对称性
左端和右端的期望公式略有不同:
- 左端:
(1 - nowl) * a[l].first - 右端:
(1 - a[r].first) * nowr
这不是错误,而是算法的巧妙设计!
八、知识点总结
核心算法技巧
-
✅ 概率期望计算
- 失落期望 = P(昨天高兴) × P(今天不高兴)
- 连续使用项目的期望累加
-
✅ 贪心策略
- 边界优化(第1天和第k天)
- 中间贪心选择
-
✅ 双指针
- 左右两端维护不同概率的项目
- 动态决策
-
✅ 批量处理优化
- 相同概率项目一次性处理
- 减少循环次数
适用场景
- ✅ 概率期望问题
- ✅ 序列安排优化
- ✅ 贪心策略设计
- ✅ 状态转移
九、总结
算法精髓
本题采用的双端贪心 + 概率期望算法的核心思想:
- 边界优化:第1天和第k天特殊处理
- 贪心策略:根据当前状态动态选择
- 批量优化:相同概率项目一次性处理
- 期望计算:精确的数学公式
- 1
信息
- ID
- 4072
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者