1 条题解
-
0
这是一份基于你提供的代码的详细题解。
题目分析
这道题主要包含两个部分:
- 维护每个单位的生命值状态:由于伤害是概率性的,我们需要维护每个单位剩余生命值的概率分布。
- 处理“结界”技能的询问:计算在选定 个单位中,恰好命中某一个存活单位的概率。这涉及到组合概率问题。
算法详解
1. 维护生命值 DP (
calc1函数)由于每个单位的血量上限 很小 (),我们可以对每个单位分别维护一个 DP 数组。
设 表示第 个单位剩余生命值为 的概率。
- 初始时,对于单位 ,有 ,其余为 0。
当对单位 施放“锁定”技能时(命中概率 ): 我们需要更新 数组。对于任意 ,生命值从 变为 的概率是 ,保持为 的概率是 。 转移方程如下:
- $f_{new}[id][j] = f_{old}[id][j] \times (1 - p) + f_{old}[id][j+1] \times p$ (对于 )
- $f_{new}[id][m_{id}] = f_{old}[id][m_{id}] \times (1 - p)$
- $f_{new}[id][0] = f_{old}[id][0] + f_{old}[id][1] \times p$ (生命值降为0或原本就是0都算死亡)
这一步的时间复杂度为 。代码中的
calc1函数正是实现了这个逻辑。2. 处理“结界”技能 (
calc3和calc2函数)对于选定的 个单位,如果我们要计算命中单位 的概率,公式为:
$$P(\text{命中} u) = P(u \text{存活}) \times \sum_{j=0}^{k-1} P(\text{除} u \text{以外恰好有} j \text{个单位存活}) \times \frac{1}{j+1} $$这里 是因为如果包括 在内共有 个单位存活,命中 的概率是均等的。
为了计算这个值,我们需要知道“除 以外恰好有 个单位存活”的概率。如果对每个 都做一次 的背包 DP,总复杂度会变成 ,在 较大时无法接受。
优化方案:退背包(除去一个物品的背包 DP)
-
整体 DP (
calc3): 首先计算 个单位的整体情况。设 表示这 个单位中,恰好有 个单位存活的概率。 这其实是一个生成函数的卷积,或者看作 0/1 背包问题。 设单位 存活的概率为 ,死亡的概率为 。 转移方程:。 时间复杂度 。 -
退背包 (
calc2): 对于每个单位 ,我们需要从整体数组 中“撤销”掉单位 的贡献,得到临时数组tmp,其中tmp[j]表示除 以外恰好 个存活的概率。 根据整体 DP 的方程,我们有: 移项得: 即:- 特殊情况:如果 (单位 必定存活),则 ,所以 。
- 代码中的
calc2实现了这个逆推过程。f[id][0]即为 ,1 - f[id][0]即为 。计算需要用到模逆元。
-
统计答案: 利用
tmp数组计算最终概率:ans= 最后乘以 存活的概率 。
复杂度分析
- 锁定技能:每次 。
- 结界技能:
calc3耗时 。- 对于 个目标,每个调用
calc2耗时 ,共 。 - 单次结界总耗时 。
- 总复杂度: 。 带入数据 ,最大计算量约为 $2 \times 10^7 + 1000 \times 40000 \approx 6 \times 10^7$,在 6000ms 的时限内非常充裕。
代码对应解读
f[i][j]:DP 数组,表示第i个单位剩余血量为j的概率。calc1(id, p):执行锁定技能,更新f[id]。calc3():对当前询问的 个单位进行 的背包 DP,计算 数组。calc2(id):执行退背包操作,计算除去单位id后的 数组。main函数最后部分:计算所有技能结束后每个单位的期望生命值 。
总结
这是一道结合了 概率 DP 和 背包 DP(含退背包技巧) 的好题。代码逻辑清晰,利用退背包将每次询问的复杂度从 优化到了 ,从而通过了题目。
- 1
信息
- ID
- 6087
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者