算法笔记: [背包问题] 洛谷P3985 不开心的金明
题目详情
原题链接:洛谷P3985 不开心的金明
不开心的金明
Description
金明今天很不开心,家里购置的二手房就要领钥匙了,房里并没有一间他自己专用的很宽敞的房间。更让他不高兴的是,妈妈昨天对他说:“你需要购买哪些物品,怎么布置,你说了不算(有很大的限制),而且不超过W元钱。”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的W元。于是,他把每件物品规定了一个重要度整数\(p_i\)表示。他还从因特网上查到了每件物品的价格\(v_i\)(都是整数元)。
妈妈看到购物单后进行了审查,要求购物单上所有的物品价格的极差(最贵的减去最便宜的)不超过3(当然金明至今不知道为什么会这样)。他希望在不超过W元(可以等于W元)的前提下,使购买的重要度总和\(\sum p_i\)的最大。
请你帮助金明设计一个满足要求的购物单,你只需要告诉我们重要度的最大的和。
Input Format
输入的第1行,为两个正整数,用一个空格隔开:
n W (其中W表示总钱数,n为希望购买物品的个数。)
从第2行到第n+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数v p (其中v表示该物品的价格,p表示该物品的重要度)
Output Format
输出只有一个正整数,为不超过总钱数的物品的重要度的总和的最大值
I/O Sample #1
Sample Input #1
5 10
2 800
5 400
5 300
3 400
2 200
Sample Output #1
1600
Hint
\(1 \le N \le 100\)
\(1 \le W \le 10^9\)
\(1 \le vi \le 10^9\)
对所有的 \(i=1,2,3,…,N\),$ min(v_i) \le v_i \le min(v_i)+3$.
\(1 \le p_i \le 10^7\)
疑惑的来源
起初我只是把它当作简单的背包问题,但是观察了数据规模后发现商品的单价\(v_i\)和限制的总价格\(w\)过于庞大,以至于无法开出这么大规模的数组dp[w]
,所以解法势必根据数据规模进行优化。而我发现有以下两种解法:
解法一 优化价格 使用三维数组dp[i][w][k]
(恰当的解法)
优化价格
找出商品中的最小价格\(minp\), 然后将每个价格减去最小价格:\(p_i-minp\), 由于题目限制商品价格的极差不大于\(3\),那么这样处理过后的商品价格只有\(p`_i=0,1,2,3\)这几种情况,同时商品总价\(w\)也被压缩成\(sumv = \sum_{i = 1}^{n} p'_i\).
使用三维数组dp[i][w][k]
首先描述一下数组dp
各个维度的意义
i
表示遍历到第i
个商品时的状态
对于遍历到的每一个物品,金明都可以选择买或者不买w
表示当前的预算大小
相当于子背包或者子问题,最终问题的答案(即在金明妈妈给出的预算内怎么买商品)由较小预算的状态推演过来.k
表示购买的商品数量
例如当dp[5][10][4]
表示金明希望购买5件商品,但妈妈只给出10块钱预算的情况下,买4件商品的最优方案.(可能不存在)
如何遍历三维数组dp[][][]
直接上代码
// 从清单中逐一挑选商品
for (int i = 1; i <= n; i ++)
/*
由于我们不知道优化后的总预算是多少,
所以这里暂且用优化后的商品总价代替.
*/
for (int j = sumv; j >= p[i]; j --)
// 购买数量亦是状态里的一部分
for (int k = n; k >= 1; k --)
// 确保不超出预算则更新状态
if ((long long) minv * k + j <= w)
// 在此处更新dp的状态
dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - p[i]][k - 1] + c[i]);
else
// 没得选择
dp[i][j][k] = dp[i - 1][j][k];
然后就是状态转移方程
dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - p[i]][k - 1] + c[i]);
状态方程的具体意思是,当前dp[i][j][k]
的取值由买和不买决定:
如果买,则取值为上一个商品i - 1
的各种状态中,花费j - p[i]
,共购买了k
件商品的状态,由该状态下的重要度加上当前商品的重要度,即dp[i][j - p[i]][k - 1] + c[i]
为取值;
如果不买或者超出总预算(else
中的语句),则直接继承上一个商品i - 1
同维度下的状态即可.
总之,可买可不买取其大者,买不了则继承之前的状态.
其实还有优化的余地:滚动二维数组
以下是优化后的代码
for (int j = sumv; j >= p[i]; j --)
for (int k = n; k >= 1; k --)
if ((long long) minv * k + j <= w)
dp[j][k] = max(dp[j][k], dp[j - p[i]][k - 1] + c[i]);
优化后尤其要注意状态维度的遍历顺序,因为维度i
被优化掉了,那么更新状态时,数组dp[][]
中同时存在着新旧状态,新状态需要由旧状态中的小者更新而来,所以我们倒序遍历各个状态维度,即可保证还未被转移的旧小状态,不会被新小状态替代.
解法二 01背包 + 贪心(不太恰当的解法)
根据数据规模的大小,分别采用两种方法,当规模较小时,按照普通的01背包问题解决;当规模较大时,使用贪心算法,题解中给出的理由是:当商品的最小价格\(minp\)达到阈值时,将预算统统购买最便宜或最贵的商品,两者可购买的数量是相等的.
对于一维数组dp
的规模极限,由于商品价格极差不大于3,所以可能有\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ≤100\).当上述情况成立时,题解认为可以使用贪心.将商品清单按重要度排序,自大而小取走即可,并且题解推导出当\(minp > 300\)时,上述情况成立.
勘误
勘误推理参考:君义_noip的证明
实际上\(minp > 300\)只是\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ≤100\)的必要不充分条件
当\(W\)被\(minp\)整除时,\(\left \lfloor \frac{W}{minp} \right \rfloor =\left \lfloor \frac{W}{minp+3} \right \rfloor ≤100\)就已经不成立了,此时使用贪心得到的结果可能是错误的.
例如以下数据
800 3
400 3
400 4
401 5
此时贪心给出的答案为5, 而正确答案为7
标签:状态,le,洛谷,P3985,商品,minp,dp,金明 From: https://www.cnblogs.com/yousa/p/17278869.html