1 条题解
-
0
一、题意理解
有 个坑,按顺序播种。
- 每个坑播种需要 单位时间。
- 第 个坑有概率 播错种子。
- 铲除一个种子也需要 单位时间。
- 在任何时刻,可以花费 时间检查 所有已播种的坑,然后从当前坑往前一直铲到 第一个出错的位置(包括这个位置)。
目标是:选择最优的检查策略,使得播完所有坑的 期望时间 最小。
二、关键点分析
-
检查的作用
如果发现错误,可以立即铲除从当前坑到第一个错误之间的所有种子(包括错误位置),避免后续在这些错误基础上继续播种浪费更多时间。 -
铲除规则
假设当前检查时已经播种到位置 ,检查发现最早出错的坑是 (),那么需要铲除 这些坑的种子,铲除时间 = 这些坑的数量。 -
决策点
我们可以在播种任意一个坑之后决定是否检查。最优策略是动态决定何时检查,以最小化总期望时间。
三、动态规划状态设计
设 表示 已经正确播种完前 个坑 的最小期望时间。
注意这里“正确播种完”意味着:
- 前 个坑都播种过且当前没有错误(可能经过检查铲除后重新播种正确)。
初始:(没有坑时时间为 0)。
四、状态转移
考虑从 转移到 ():
策略:从第 个坑开始播种,一直播种到第 个坑,然后进行一次检查。
1. 播种阶段
播种 个坑,时间 = 。
2. 检查阶段
花费 时间检查。
3. 可能的结果
检查时,我们看前 个坑中 最早出错 的位置。
设最早出错位置是 ()的概率:
- 前 个坑保证正确(因为 状态是正确播种完前 个坑)。
- 所以最早出错位置 必须满足: 到 全对,第 个错。
概率 = 。
如果 表示 到 全对,概率 = 。
4. 铲除与恢复时间
如果最早出错在 ():
- 需要铲除 到 的种子,数量 = 。
- 然后从 开始重新正确播种到 ,这部分的期望时间 = 。
如果全对():
- 不需要铲除,直接进入状态 ,额外时间 = 。
五、转移方程
设 。
那么: $ dp[j] = \min_{0 \le i < j} \left\{ dp[i] + (j-i) + t + \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \cdot \big( (j-k+1) + (dp[j] - dp[k-1]) \big) \right] \right\} $ 其中对于 (全对)的情况,贡献为 ,已经隐含在求和中( 只到 )。
但这里 出现在等式两边,需要整理。
设 $A = dp[i] + (j-i) + t + \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \cdot (j-k+1) \right]$
设 $B = \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \cdot dp[k-1] \right]$那么: $ dp[j] = A - B + \left( \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \right] \right) dp[j] $
设 $C = \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \right]$,则: 注意 ,因为这是所有可能情况。
所以 。
因此:
六、最终转移方程
对于 : $ dp[j] = \min \left\{ \frac{ dp[i] + (j-i) + t + \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \cdot (j-k+1) \right] - \sum_{k=i+1}^j \left[ \left( \prod_{r=i+1}^{k-1} (1-p_r) \right) p_k \cdot dp[k-1] \right] }{ \prod_{r=i+1}^j (1-p_r) } \right\} $
七、算法实现
我们可以用动态规划,在计算 时枚举 ,并维护前缀积来快速计算概率乘积。
复杂度 可优化到 ,因为 , 可过。
八、样例验证
样例:
n=3, t=1 p = [0.00001, 0.5, 0.00001]输出:
8.000080000800008用上述 DP 计算可得该结果。
九、代码框架(C++)
#include <cstdio> #include <cstring> #include <algorithm> const int N = 3006; int n, t; double p[N], f[N]; int main() { #ifdef Ezio freopen("10.in", "r", stdin); freopen("10.out", "w", stdout); #endif scanf("%d%d", &n, &t); for (int i = 1; i <= n; i++) scanf("%lf", &p[i]), p[i] = 1 - p[i]; f[n] = 0; for (int i = n - 1; i >= 0; i--) { double cur = t + 1 + (1 - p[i + 1]) + p[i + 1] * f[i + 1]; double temp = p[i + 1]; f[i] = cur; for (int j = 2; i + j <= n; j++) { temp *= p[i + j]; cur += 2 - temp + temp * f[i + j] - temp * f[i + j - 1]; f[i] = std::min(f[i], cur); } f[i] /= p[i + 1]; } printf("%.8lf\n", f[0]); return 0; }
十、总结
本题的关键在于:
- 将最优检查策略建模为动态规划。
- 状态 表示正确播种前 个坑的最小期望时间。
- 转移时考虑从 播种到 后检查,根据最早出错位置计算期望时间。
- 利用概率公式化简转移方程。
该解法可以处理 的数据范围。
- 1
信息
- ID
- 4557
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者