用一维和二维数组解决超大背包问题

问题描述

特大背包问题

  现在有一个容量为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
  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; }

 大家如果看到我有什么不对的,欢迎指出哦!

 
posted @ 2019-10-31 15:10  阿璃阿狸  阅读(412)  评论(0)    收藏  举报