例题
分析
本题是一个很明显的动态规划题目,如果使用枚举法来做的话,时间复杂度是指数级的,当数据量大的时候很容易超出时间限制。
根据题目要求来看,由于每个物品只能购买一次,所以很明显我们可以使用01背包的算法来做本题。
在做01背包的题目时,我们首先要弄清楚最重要的条件:
(1).物体有哪些属性?什么属性是要被装的?
(2).背包装什么?
(3).背包容量多大?
做背包时题目会给出N多个属性信息,我们需要在这些信息中找到正确的选项作为放入背包的选项。
从本题来看,我们一件物品有2个属性:
1.价格 2.满意度。
确定哪个是被装的属性可以从题目给的限制入手,题目有两个要求:
1.购买物品的总价格不能超过本金N;
2.购买物品的数量不能超过m;
由于每个物品只能被购买一次,购买物品的数量不能超过m,所以我们可以确定被装的物品属性就是物品的价格了,由此我们也可以确定背包的容量尺度为本金N。
于是一个简单的01背包模型就可建立起来了
对照经典的背包模型:
砖块重量 -----物品价格
砖块价值------物品满意度
重量背包------价格背包
完成了背包模型建立,本题还有一个难点:主件和附件问题。
当我们购买附件时,我们必须购买主件,所以当我们添加附件放入背包的时候,附件的价格为(附件价格+主件价格),例如:
当我们购买灯泡的时候,灯泡价格是5元,灯泡是台灯的附件,台灯价格为25元,购买灯泡之前必须购买台灯,所以我的本金最少要减去30元。
我们可以将灯泡的价格与台灯的价格合并在一起,作为一个新物体来看待,但是由于一个主件可能有多个附件,所以如果如此操作的话会忽略N个附件和主件一起购买的情况,例如:
主件为:电脑(8000元)
附件为:打印机(800元)、复印机(600元)
若按顺序遍历,遍历到打印机和复印机时,仅仅将打印机的价格改为(8000+800)元、打印机价格改成(8000+600),我们的确能判断只买电脑、只买电脑和打印机、只买电脑和复印机这三种情况,还缺少购买电脑、打印机、复印机(8000+800+600)这中情况。
所以我们附件不应该单独遍历,而应该将主件与附件组合起来,只有遍历到主件的时候才判断情况。
当我们做01背包的时候,我们判断是否将物品A的放入背包时,要判断将其他物品拿出放入A的价值大还是不放入A保持原状的价值更大,来看是否要将物品A放入,例:
背包 dp[100] ={0};
物品 A 重量 70 ,价值 70
物品 B 重量 40 ,价值 65
在放入A之前,背包以及放入了一个物体B,则背包中最大价值为65,占用了40的空间,所以我们可以将dp[40]~dp[100]赋值为65;(注意:此处dp[i] = K为,当背包只有 i 这么大时能装入的最大价值K)
判断我们是否将A放入背包时,需要将背包空间腾出来,需要腾出70的空间,由于背包容量为100,所以如果放入A的话,背包内物品的总价值为dp[30] + 70 = 0 + 70;
如果不放入A的话,背包容量为70时所放物品最大价值为dp[70] = 65,小于70,所以要拿出B,放入A将矩阵更新为dp[70]~dp[100] = 70。
代码
代入本题时,我们只需要多判断加入A的时候是否加入附件的情况。
首先我们先将主件与附件合并,由于题目提示每个主件最多只有1~2个附件,所以我们只需要在主件后面预留两个空间用来存放附件信息:
int x;
cin>>x;
vector<vector<int>> data(x+1,vector<int>(6,0));//最多两个附件,所以申请6的空间大小
while(x--)
{
int val,w,q;
cin >> val>>w>>q;
if(q > 0)
{
if(data[q][2] == 0)
{
data[q][2] = val/10;
data[q][3] = w;
}
else
{
data[q][4] = val/10;
data[q][5] = w;
}
}
else if(q == 0)
{
data[k][0] = val/10;
data[k][1] = w;
}
k++;
}
接下来就是常规01背包的问题了,data[i][0]、data[i][2] 、data[i][4]分别为主件价格、附件1价格、附件2价格,data[i][1]、data[i][2] 、data[i][3]分别为主件重要程度、附件1重要程度、附件2重要程度。
代码就不多解释了,整体代码如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,x;
cin>>n;
cin>>x;
vector<vector<int>> data(x+1,vector<int>(6,0));
vector<int> dp(n/10+1,0);
int k = 1;
while(x--)
{
int val,w,q;
cin >> val>>w>>q;
if(q > 0)
{
if(data[q][2] == 0)
{
data[q][2] = val/10;
data[q][3] = w;
}
else
{
data[q][4] = val/10;
data[q][5] = w;
}
}
else if(q == 0)
{
data[k][0] = val/10;
data[k][1] = w;
}
k++;
}
for(int i = 0;i < data.size();i++)
{
for(int j = dp.size()-1;j >= data[i][0];j--)//一定要从后向前遍历,不然会有物品被重复放入
{
dp[j] = max(dp[j],dp[j-data[i][0]] + data[i][0]*data[i][1]);
if( j >= data[i][0] + data[i][2])
dp[j] = max(dp[j],dp[j-(data[i][0] + data[i][2])]
+ data[i][0]*data[i][1]
+ data[i][2]*data[i][3]);
if( j >= data[i][0] + data[i][4])
dp[j] = max(dp[j],dp[j-(data[i][0] + data[i][4])]
+ data[i][0]*data[i][1]
+ data[i][4]*data[i][5]);
if( j >= data[i][0] + data[i][2] + data[i][4])
dp[j] = max(dp[j],dp[j-(data[i][0] + data[i][2]+ data[i][4])]
+ data[i][0]*data[i][1]
+ data[i][2]*data[i][3]
+ data[i][4]*data[i][5]);
}
}
cout << dp[n/10]*10;
}