笔记:洛谷 P3985 不开心的金明

算法笔记: [背包问题] 洛谷P3985 不开心的金明

题目详情

原题链接:洛谷P3985 不开心的金明

不开心的金明

Description

  金明今天很不开心,家里购置的二手房就要领钥匙了,房里并没有一间他自己专用的很宽敞的房间。更让他不高兴的是,妈妈昨天对他说:“你需要购买哪些物品,怎么布置,你说了不算(有很大的限制),而且不超过W元钱。”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的W元。于是,他把每件物品规定了一个重要度整数pi表示。他还从因特网上查到了每件物品的价格vi(都是整数元)。
  妈妈看到购物单后进行了审查,要求购物单上所有的物品价格的极差(最贵的减去最便宜的)不超过3(当然金明至今不知道为什么会这样)。他希望在不超过W元(可以等于W元)的前提下,使购买的重要度总和pi的最大。
  请你帮助金明设计一个满足要求的购物单,你只需要告诉我们重要度的最大的和。

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

1N100
1W109
1vi109

对所有的 i=1,2,3,,Nmin(vi)vimin(vi)+3.
1pi107

疑惑的来源

  起初我只是把它当作简单的背包问题,但是观察了数据规模后发现商品的单价vi和限制的总价格w过于庞大,以至于无法开出这么大规模的数组dp[w],所以解法势必根据数据规模进行优化。而我发现有以下两种解法:

解法一 优化价格 使用三维数组dp[i][w][k](恰当的解法)

优化价格

  找出商品中的最小价格minp, 然后将每个价格减去最小价格:piminp, 由于题目限制商品价格的极差不大于3,那么这样处理过后的商品价格只有pi=0,1,2,3这几种情况,同时商品总价w也被压缩成sumv=i=1npi.

使用三维数组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,所以可能有Wminp=Wminp+3100.当上述情况成立时,题解认为可以使用贪心.将商品清单按重要度排序,自大而小取走即可,并且题解推导出当minp>300时,上述情况成立.

勘误

勘误推理参考:君义_noip的证明

实际上minp>300只是Wminp=Wminp+3100必要不充分条件
Wminp整除时,Wminp=Wminp+3100就已经不成立了,此时使用贪心得到的结果可能是错误的.
例如以下数据

800 3
400 3
400 4
401 5

此时贪心给出的答案为5, 而正确答案为7

posted @   YusJade  阅读(153)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示