乍一看好像每一次让一个怪物扣血的概率都和场上存活的怪物数量有关系,又注意到n很小,容易想到使用状态压缩。记 dp[s][i] 为攻击了 i 次,有 s 这个状态的怪物死了的概率。那么最后的答案就是攻击m次之后每个状态的popcount乘以概率乘以其余怪物活着的方案。记 g[s][i] 为砍了 i 次,有 s 这些怪物活着的方案数。那么可以分别得到 dp 和 g 的状态转移方程:
std::vector<int> a(n); for (int i = 0; i < n; ++i)std::cin >> a[i];
std::vector<int> sum(1 << n); for (int s = 0; s < 1 << n; ++s) for (int i = 0; i < n; ++i) if (s >> i & 1)sum[s] += a[i];
std::vector<std::vector<longdouble>> g(m + 1, std::vector<longdouble>(1 << n)); //噶i刀,存活状态为s的方案数量。 g[0][0] = 1; for (int s = 0; s < 1 << n; ++s) { for (int k = 0; k < n; ++k) { if ((s >> k) & 1) { int s0 = s ^ (1 << k); for (int i = 0; i <= m; ++i) { for (int j = 0; j <= std::min(a[k] - 1, i); ++j) { g[i][s] += g[i - j][s0] * C[i][j]; } } break; } } }
std::vector<std::vector<longdouble>> dp(m + 1, std::vector<longdouble>(1 << n)); //噶i刀,状态为s的挂了的概率 dp[0][0] = 1; for (int s = 0; s < 1 << n; ++s) { int cur = __builtin_popcount(s); int cnt = n - cur; for (int now = sum[s]; now < m; ++now) { int rem = ((1 << n) - 1) ^ s; for (int j = 0; j < n; ++j) { if (!((s >> j) & 1)) { if (a[j] - 1 <= now - sum[s])dp[now + 1][s | (1 << j)] += dp[now][s] * C[now - sum[s]][a[j] - 1] / cnt; } } dp[now + 1][s] += dp[now][s] / cnt; } }
for (int s = 0; s < 1 << n; ++s) { for (int i = 0; i <= m; ++i) { if (i < sum[s])continue; int rem = ((1 << n) - 1) ^ s; dp[i][s] *= g[i - sum[s]][rem]; } }
longdouble ans = 0; for (int s = 0; s < 1 << n; ++s) { if (m < sum[s])continue; int cnt = __builtin_popcount(s); ans += dp[m][s] * cnt; } std::cout << std::fixed << std::setprecision(5) << ans << '\n'; return0; }