1 条题解
-
0
这道题可以明显看出01背包的痕迹,n个物品最多选择一个,问最终的重量数,按照常规的01背包来看
dp[i][j]应该是从前i个物品中选取凑满容量j的最大价值,不过这是在01背包上做了变形,需要具体情况具体分析。这里问的是能得到重量数,并且本次得到的重量数可能会和以前的重量数有重叠,所以dp的值就不能记录方案数。
其次是观察砝码的重量的获取,观察中间状态:当选取到第i个物品时,本次能获得的重量数和i-1个能获得的所有的重量数有关,在此基础上,每个砝码可能有三种情况,那就是选,选正的情况,和选负的情况
因此需要记录下第i个物品能获得的重量,然后更新下一个物品能获得的重量。
那么可以令**
dp[i][j] = true/false表示枚举到第i个物品时,能否通过这i个物品获得j重量**这里又有问题,负的重量没有下标,但是我们可以通过所有砝码的重量sum获取整体重量的范围,即[-sum , sum],那么整体将其偏移一个sum来处理,范围变为[0 , 2 * sum]
初始化:使用0个砝码也能获得重量0,用于后续递推,dp[sum] = true
降维后来做代码如下:
#include<bits/stdc++.h> using namespace std; #define ll long long int n; int w[105]; int main() { cin >> n; int sum = 0; for (int i = 1; i <= n; i++) { cin >> w[i]; sum += w[i]; } int cap = 2 * sum + 1; //获得背包的容量,将[-sum,sum]转换到[0,2*sum]区间中 int offset = sum; //sum就是相对的偏移量,统一重量向右偏移offset vector<bool> dp(cap, false); //dp[x]:表示枚举到当前砝码时该重量能称出来 dp[offset] = true; //初始时0中重量能称出来 for (int i = 1; i <= n; i++) {//枚举n个砝码 vector<bool> now = dp;//用于记录当前层的状况,因为下一层的状态会在递推过程中更改 for (int j = 0; j < cap; j++) {//枚举当前层中所有的容量 if (now[j]) {//在砝码范围内该重量能得到,就可以用该重量更新下一层的重量 if (j - w[i] >= 0)dp[j - w[i]] = true; if (j + w[i] < cap)dp[j + w[i]] = true; } } } int ans = 0; //实际统计重量时从1开始统计 for (int j = offset + 1; j < cap; j++)if (dp[j])ans++; cout << ans; return 0; }为什么这里降维内层循环不用倒着枚举?
因为一般的01背包倒着递推是因为它的递推需要通过上一层的枚举递推得到本层的结果,并且是从前往后推,会导致先推导本层前面的值并改变,而本层后面需要前面的值时,它需要的是上一层的值,而此时它已经改变了,所以需要倒着递推
而这里重量的获得并非仅从其前面的值获得,而有可能是后面的值递推而来,因此这里需要记录上一层的值,接着进行递推
而一般的01背包重量都是正的,后面的值由上一层前面的值获取,因此从前往后推导,而为了简化步骤倒着枚举,其实记录上一层的值也行,只是没这个实现简便
信息
- ID
- 553
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 87
- 已通过
- 28
- 上传者