1 条题解

  • 0
    @ 2025-12-11 9:24:29

    这是一份基于你提供的代码的详细题解。

    题目分析

    这道题主要包含两个部分:

    1. 维护每个单位的生命值状态:由于伤害是概率性的,我们需要维护每个单位剩余生命值的概率分布。
    2. 处理“结界”技能的询问:计算在选定 kk 个单位中,恰好命中某一个存活单位的概率。这涉及到组合概率问题。

    算法详解

    1. 维护生命值 DP (calc1 函数)

    由于每个单位的血量上限 mim_i 很小 (mi100m_i \le 100),我们可以对每个单位分别维护一个 DP 数组。

    f[i][j]f[i][j] 表示第 ii 个单位剩余生命值为 jj 的概率。

    • 初始时,对于单位 ii,有 f[i][mi]=1f[i][m_i] = 1,其余为 0。

    当对单位 idid 施放“锁定”技能时(命中概率 pp): 我们需要更新 f[id]f[id] 数组。对于任意 j>0j > 0,生命值从 jj 变为 j1j-1 的概率是 pp,保持为 jj 的概率是 1p1-p。 转移方程如下:

    • $f_{new}[id][j] = f_{old}[id][j] \times (1 - p) + f_{old}[id][j+1] \times p$ (对于 1j<mid1 \le j < m_{id}
    • $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都算死亡)

    这一步的时间复杂度为 O(mid)O(m_{id})。代码中的 calc1 函数正是实现了这个逻辑。

    2. 处理“结界”技能 (calc3calc2 函数)

    对于选定的 kk 个单位,如果我们要计算命中单位 uu 的概率,公式为:

    $$P(\text{命中} u) = P(u \text{存活}) \times \sum_{j=0}^{k-1} P(\text{除} u \text{以外恰好有} j \text{个单位存活}) \times \frac{1}{j+1} $$

    这里 1j+1\frac{1}{j+1} 是因为如果包括 uu 在内共有 j+1j+1 个单位存活,命中 uu 的概率是均等的。

    为了计算这个值,我们需要知道“除 uu 以外恰好有 jj 个单位存活”的概率。如果对每个 uu 都做一次 O(k2)O(k^2) 的背包 DP,总复杂度会变成 O(k3)O(k^3),在 kk 较大时无法接受。

    优化方案:退背包(除去一个物品的背包 DP)

    1. 整体 DP (calc3): 首先计算 kk 个单位的整体情况。设 g[j]g[j] 表示这 kk 个单位中,恰好jj 个单位存活的概率。 这其实是一个生成函数的卷积,或者看作 0/1 背包问题。 设单位 vv 存活的概率为 Pv=1f[v][0]P_v = 1 - f[v][0],死亡的概率为 Qv=f[v][0]Q_v = f[v][0]。 转移方程:g[j]=g[j]×Qv+g[j1]×Pvg[j] = g[j] \times Q_v + g[j-1] \times P_v。 时间复杂度 O(k2)O(k^2)

    2. 退背包 (calc2): 对于每个单位 uu,我们需要从整体数组 gg 中“撤销”掉单位 uu 的贡献,得到临时数组 tmp,其中 tmp[j] 表示除 uu 以外恰好 jj 个存活的概率。 根据整体 DP 的方程,我们有: g[j]=tmp[j]×Qu+tmp[j1]×Pug[j] = tmp[j] \times Q_u + tmp[j-1] \times P_u 移项得: tmp[j]×Qu=g[j]tmp[j1]×Putmp[j] \times Q_u = g[j] - tmp[j-1] \times P_u 即:

      tmp[j]=g[j]tmp[j1]×PuQutmp[j] = \frac{g[j] - tmp[j-1] \times P_u}{Q_u}
      • 特殊情况:如果 Qu=0Q_u = 0(单位 uu 必定存活),则 g[j]=tmp[j1]g[j] = tmp[j-1],所以 tmp[j]=g[j+1]tmp[j] = g[j+1]
      • 代码中的 calc2 实现了这个逆推过程。f[id][0] 即为 QuQ_u1 - f[id][0] 即为 PuP_u。计算需要用到模逆元。
    3. 统计答案: 利用 tmp 数组计算最终概率: ans = j=0k1tmp[j]×1j+1\sum_{j=0}^{k-1} tmp[j] \times \frac{1}{j+1} 最后乘以 uu 存活的概率 (1f[u][0])(1 - f[u][0])

    复杂度分析

    • 锁定技能:每次 O(max(hi))O(100)O(\max(h_i)) \approx O(100)
    • 结界技能
      • calc3 耗时 O(k2)O(k^2)
      • 对于 kk 个目标,每个调用 calc2 耗时 O(k)O(k),共 O(k2)O(k^2)
      • 单次结界总耗时 O(k2)O(k^2)
    • 总复杂度O(Qmax(hi)+Cn2)O(Q \cdot \max(h_i) + C \cdot n^2)。 带入数据 n200,Q200000,C1000n \le 200, Q \le 200000, C \le 1000,最大计算量约为 $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():对当前询问的 kk 个单位进行 O(k2)O(k^2) 的背包 DP,计算 gg 数组。
    • calc2(id):执行退背包操作,计算除去单位 id 后的 tmptmp 数组。
    • main 函数最后部分:计算所有技能结束后每个单位的期望生命值 j×f[i][j]\sum j \times f[i][j]

    总结

    这是一道结合了 概率 DP背包 DP(含退背包技巧) 的好题。代码逻辑清晰,利用退背包将每次询问的复杂度从 O(k3)O(k^3) 优化到了 O(k2)O(k^2),从而通过了题目。

    • 1

    信息

    ID
    6087
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者