用一维和二维数组解决超大背包问题
问题描述
特大背包问题
现在有一个容量为C的背包和N个重量和价值已知的物品. 现在要从这n个物品中挑选出一些物品, 使得选择的物品的总重量不超过背包的容量, 且总价值最大.此题的数据范围:
1 <= C <= 10^8(10的8次方)
1 <= N <= 100
输入描述
有多组测试数据. 第一行一个正整数T(T<=15), 表示测试数据组数.
对于每组测试数据:
第一行两个正整数N和C, 分别表示物品的数量和背包的容量.
接下来N行, 每行两个正整数w,v ,分别表示对应物品的重量和价值(1<= w <= 10^7, 1<= v <= 100)
1 <= C <= 10^8(10的8次方)
1 <= N <= 100
输入描述
有多组测试数据. 第一行一个正整数T(T<=15), 表示测试数据组数.
对于每组测试数据:
第一行两个正整数N和C, 分别表示物品的数量和背包的容量.
接下来N行, 每行两个正整数w,v ,分别表示对应物品的重量和价值(1<= w <= 10^7, 1<= v <= 100)
输出描述
输出一个正整数, 表示在所选物品不超过背包容量的情况下, 能够得到的最大价值.
输出一个正整数, 表示在所选物品不超过背包容量的情况下, 能够得到的最大价值.
输入样例
1
3 10
5 10
5 10
4 12
输出样例
22
1
3 10
5 10
5 10
4 12
输出样例
22
问题分析
建议阅读完我上篇博文再来看这篇,有了0-1背包的基础后,我们可以分析
因为C的数据范围过大,所以不能再把它当成二维数组的纵坐标了,仔细想一想,其实可以把价值当做纵坐标来填表。
因为价值的数据范围还比较合理,那么表格中的数据就是前i个物品挑选价值总和为j时背包的最小容量了(不存在就赋值INF)。
然后填完二维数组后,我们直接找到最后一行,从最高的价值即物品价值总和一直往前推(也就是递减)背包最小容量时,
会有一个临界点,就是最小容量,刚好<=C,此时又正好满足背包价值最大,因此这个点的纵坐标就是题目的答案了。
#include<iostream> using namespace std; #define N 5500 //注意这里的范围要根据物品价值总和来确定 #define min(a,b) a>b?b:a #define LL long long #define INF 0x3f3f3f3f; int c[N][N];//c[i][j]表示在前i个物品中挑出价值总和为j时背包容量的最小值(不存在就赋值INF)。 int Fill(int n,int C,int w[],int v[]) { int i,j,vsum=0; for(i=1;i<=n;i++) vsum=vsum+v[i];//算出总的最大的价值 for(j=0;j<=vsum;j++) { c[0][j]=INF; //这里初始化要注意 } c[0][0]=0;//这里一定要初始化为0 前0个物品价值为0时背包最小容量也为0 for(i=1;i<=n;i++) { for(j=1;j<=vsum;j++)//这里正序逆序都行,因为每次循环是往二维数组中填数据,而不是覆盖 { if(j<v[i])//当前物品价值大于预设价值和,不用放入 这个判断其实为了下面满足j-v[i]>=0 c[i][j]=c[i-1][j]; else c[i][j]=min(c[i-1][j],c[i-1][j-v[i]]+w[i]); } } for(j=vsum;j>=0;j--)//二维数组最后一行逆序寻找那个临界点:前n个物体下,价值为j的背包的最小容量 { if(c[n][j]<=C) return j; } } int main() { int ncase; int n,C,w[N],v[N]; cin>>ncase;//输入测试数据组数 while(ncase--) { cin>>n>>C;//每组的物品个数与背包总容量 for(int i=1;i<=n;i++) cin>>w[i]>>v[i];//输入每个物品的重量及价值 cout<<Fill(n,C,w,v)<<endl;//输出返回的那个价值 } return 0; }
上述是二维数组的做法
下面来介绍一维数组的做法
和0-1背包类似,内层for循环要逆序,因为每一次外层循环都会更新一维数组,而后面的值又是由前面的值构成的,所以要逆序更新
#include<iostream> #include<stdio.h> using namespace std; //#define LL long long 可以直接用int,int和long是一样的,也没必要用long long #define N 5500 //如果这里N范围设得小而测试数据很大的话,会出现除0越界错的警告, const int INF=0x3f3f3f3f;//10的九次方级别 double c[N],w[N],C; //因为N是其他数组的最大下标,所以要兼顾全局 这里int也可以,不过设为double更加保险,范围会大一些 int v[N]; int n,vsum; int main() { int nCase;//测试数据组数 cin>>nCase; while(nCase--) { scanf("%d%lf",&n,&C);// 每组的物品个数与背包总容量 vsum=0;//当前总价值初始化为0 //输入每个物品的重量及价值 并累积总价值 for(int i=1;i<=n;i++) { scanf("%lf%d",&w[i],&v[i]); vsum+=v[i]; } //这里一定要初始化为较大的数,否则结果就不对 for(int j=0;j<=vsum;j++) c[j]=INF; c[0]=0;//0价值物品容量为0 //c[j]开始表示背包价值为j时所需要的最小容量 从j=vsum开始 for(int i=1;i<=n;i++) { //这里逆序的原因是后面的值是由前面的值决定的,如果先修改前面的值,会让后面的值出错,所以从后面的值先开始 for(int j=vsum;j>=0;j--) {
if(j>=v[i])//这里j>=v[i]是因为j<v[i]是更新一维数组没有意义 并且要满足j-v[i]>=0
c[j]=min(c[j],c[j-v[i]]+w[i]); } // for(int j=vsum;j>=v[i];j--) // c[j]=min(c[j],c[j-v[i]]+w[i]); 这两个for循环是等价的 随便哪个都可以 } //从大到小遍历价值,第一个满足条件的数组值的下标便是答案 for(int j=vsum;j>=0;j--) { if(c[j]<=C)//此时正好背包得到了最大化利用 { cout<<j<<endl; break; } } } return 0; }
大家如果看到我有什么不对的,欢迎指出哦!

浙公网安备 33010602011771号