AT_abc402_e [ABC402E] Payment Required 题解

题目传送门

思路

一道好期望 + 状压 DP 题。

首先看题,一眼期望 DP。再看数据范围,基本就可以确定解法十分暴力(\(1 \le n \le 8\))。

\(dp_{i, j}\) 代表当前已选集合为 \(i\),剩下 \(j\) 元的还可以得到的最大期望分数。

首先,成功的概率为 \(p\),失败的概率为 \(1 - p\)(这里假设 \(p = \dfrac{p_i}{100}\)\(p_i\) 为题目所给)。

由期望公式

\[\begin{aligned}\mathbb{E}[x] = \sum_{i} P(i) \times i \end{aligned} \]

可得成功的期望得分为 \(p \times (dp_{i + 2^k,j-c_k} + s_k)\)\(i + 2^k\) 为解出来 \(k\) 后的状态),失败的期望得分为 \((1-p) \times dp_{i,j-c_k}\)。其中当前状态还没有解出 \(k\),且 \(c_k \le j\)(不然就买不起了)。

这里 \(dp_{i + 2^k,j-c_k} + s_k\) 的意思是解出 \(k\) 后,钱数少了 \(c_k\) 后还能获得的最大期望分数,以及解出 \(k\) 所得的分数 \(s_k\)。而 \(dp_{i,j-c_k}\) 的意思是没有解出 \(k\),钱数还是少了 \(c_k\) 后还能获得的最大期望分数。

那结合以上内容可以得到:

\[\begin{aligned} dp_{i,j} = \max_{1 \le k \le n}\{p \times (dp_{i + 2^k,j-c_k} + s_k) + (1-p) \times dp_{i,j-c_k}\} \end{aligned} \]

其中式子里的 \(k\) 满足上述条件。

那这样就倒着枚举 \(i\),正着枚举 \(j\)

边界条件为 \(dp_{all, 0} = 0\),其中 \(all\) 指将这 \(n\) 个问题全部解决的状态。

最终答案 \(dp_{0, x}\),即还未做出一道题,也没用钱的期望钱数。

时间复杂度 \(\mathcal{O}(n \cdot x \cdot 2^n)\)

注意:下标最好从 \(0\) 开始,本题解也默认下标从 \(0\) 开始。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 11, M = 5005;

int n, x;
int s[N], c[N];
double p[N], dp[(1 << N)][M];

int main()
{
	scanf("%d%d", &n, &x);
	for (int i = 0, P; i < n; i++)
	{
		scanf("%d%d%d", &s[i], &c[i], &P);
		p[i] = P / 100.0; // 提前处理 p
	}
	for (int i = (1 << n) - 1; i >= 0; i--) // 倒序枚举 i!
	{
		for (int j = 1; j <= x; j++) // 正序枚举 j
		{
			for (int k = 0; k < n; k++)
			{
				if ((i & (1 << k)) || c[k] > j) continue; // 判断这种情况是否合法
				dp[i][j] = max(dp[i][j], p[k] * (dp[i + (1 << k)][j - c[k]] + 1.0 * s[k]) + (1 - p[k]) * dp[i][j - c[k]]);
			}
		}
	}
	printf("%.9lf\n", dp[0][x]);
	return 0;
}
posted @ 2025-07-19 22:03  lucasincyber  阅读(5)  评论(0)    收藏  举报