题解AT_arc073_b

· · 题解

原题链接

AtCoder 链接

题目大意:

N 个物品(1\leq N\leq 100),第 i 个物品重 w_i 千克(1\leq w_i\leq 10^9w_1\leq w_i\leq w_1+3),有 v_i 元的价值。现有一个最大容量为 W 的背包,即最多装 W 千克的物品。问:这个背包能装的最大价值是多少?

思路:

乍一看,这就是一道简单的 01 背包,但是 1\leq w_i\leq 10^9 导致我们不能用普通的 01 背包解决方法来解决此题。

但是,我们看到题目中有一个特殊的数据范围:w_1\leq w_i\leq w_1+3。它有什么用呢?

我们可以发现,对于任意的 2\leq i\leq Nw_i-w_1\leq 3,我们可以把每一个 w_i 都减掉 w_1,这样就不会 MLE 了。

状态表示:

这里我们看一下 $i$、$j$、$k$ 的取值范围: 显然,$1\leq i\leq n$,$0\leq k\leq i$。因为 $w$ 数组已经偏移,所以 $w_i\leq 3$。因此,$0\leq j\leq 3i$。 状态转移方程: 考虑 $dp_{i,j,k}$ 的值。 - 如果第 $i$ 个物品不选,那么 $dp_{i,j,k}=dp_{i-1,j,k}$。 - 如果 $j\geq w_i$,那么还有一种情况:第 $i$ 个物品选了,$dp_{i,j,k}=dp_{i-1,j-w_i,k}+v_i$。 综上,我们可以推出状态转移方程:$dp_{i,j,k}=\begin{cases}dp_{i-1,j-1,k-1},&j<w_i\\\min\{dp_{i-1,j,k},dp_{i-1,j-w_i,k}+v_i\},&\text{otherwise}\end{cases}$。 答案就是所有 $i=n$ 且 $j+kw_1\leq W$ 的 $dp_{i,j,k}$ 的最大值。 此算法时间复杂度为 $\operatorname{O}(n^3)$。 坑点: 1. 全程开 ```long long```,否则会炸精度! 1. 最后求答案的时候千万不要忘记 $j+kw_1\leq W$ 的特判! 1. 记得把 $w_1$ 存储起来,以便后面数值偏移和上面提到的特判! 上代码: ```cpp #include <iostream> using namespace std; long long w[101], v[101], dp[101][301][101]; int main(){ cin.tie(NULL); cout.tie(NULL); ios::sync_with_stdio(false);// cin、cout 加速 long long n, W, w_1;// 坑点 3:记得把 w[1] 存储起来 cin >> n >> W >> w_1 >> v[1]; for (int i = 2; i <= n; i++){ cin >> w[i] >> v[i]; w[i] -= w_1;// 数值偏移 } for (int i = 1; i <= n; i++){ for (int k = 1; k <= i; k++){ for (int j = 0; j <= 3 * i; j++){ // 状态转移方程 if (j < w[i]){ dp[i][j][k] = dp[i - 1][j][k]; } else { dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - w[i]][k - 1] + v[i]); } } } } // 求答案 long long ans = -1; for (int j = 0; j <= 3 * n; j++){ for (int k = 0; k <= n; k++){ if (j + k * w_1 <= W){// 坑点 2:特判要注意 ans = max(ans, dp[n][j][k]); } } } cout << ans << "\n"; return 0; } ```