CSP初赛复习-28-动态规划-多重背包

背包问题

01 背包

每种物品只有一个,每件物品只有选与不选两种状态

完全背包

每种物品有无限个

多重背包

每种物品有多个,并且多种物品数量不完全相同

分组背包

按组打包,每组最多一个

多重背包1 数据量较小

问题描述

现有 N件物品和一个容量为V的背包,每种物品有s[i]件,第 i种物品的体积是 v[i],价值是 w[i],在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值

输入格式

第一行两个整数,N和V用空格隔开,分别表示物品种数和背包容积 ( 0<=n,m<=100)

接下来有N行每行三个整数,v[i],w[i],s[i],用空格隔开,分别表示第i件物品的体积、价值和每种物品的数量

( 0<=v[i],w[i],s[i]<=100)

输出格式

输出一个整数,表示最大价值

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例

10

动态规划 -二维数组朴素算法 三层循环

大致思路

1 问题分解成N * V个子问题,N * V个状态

2 状态表示 f[i] [j] 前i种物品装入总体积不超过j的背包的最大价值

3 状态转移i-1种物品到i种物品装入背包体积为j时 分为 i为0 ,1...s[i]时的最大价值

因此需要通过需要逐一判断取最大

for(int k=0;k<=s[i];k++)

参考程序

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 110;//数组存储数量最大值物品最大数量 和 背包最大容积都是maxn 
int N,V;//N种物品 背包最大容积 
//v[i]每种物品的体积 w[i]每种物品的价值 s[i]每种物品的件数 
int v[maxn],w[maxn],s[maxn]; 
int f[maxn][maxn];//f[i][j]表示装入前i种物品不超过体积j的最大价值 
int main(){
    cin>>N>>V;//输入N种物品和背包最大容积V 
    for(int i=1;i<=N;i++){//输入每种物品的体积 价值 数量 
    	cin>>v[i]>>w[i]>>s[i];	
	}
    for(int i=1;i<=N;i++){//枚举物品1~N件和体积组合
		//计算物品和背包容积0~V整数组合计算所有最大价值 
        for(int j=0;j<=V;j++){//前i种物品最大容积不超过j时 计算最大体积 
            //枚举每种物品的件数 
            for(int k=0;k<=s[i];k++){//k从0开始,为0相等于01背包不选的情况 
                if(j>=v[i]*k){//每种逐一放入,放入此种物品件数不能超过背包总容量j 
                	f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
				}
            }
        }
    }
    cout<<f[N][V];//装入N种物品背包体积不超过V的最大价值 
    return 0;
}

一维滚动数组优化

大致思路

1 由状态转移方程可知

f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);

二维表格中,第i行只和i-1行有关

计算第j列只与j列前面的列(j-k * v[i])有关

因此,可以使用一维数组替换二维数组进行优化,可以提供存储效率

2 i行计算时使用i-1行,需要先计算后面的(如果先计算前面的,则i-1行被覆盖了) 需要从大到小循环

for(int j=V;j>=0;j--)

3 二维变一维主要存储空间重复利用,大大减少了,时间复杂度没有大幅度减少,因此这种实现方式下一题会超时

参考程序

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 110;//数组存储数量最大值物品最大数量 和 背包最大容积都是maxn 
int N,V;//N种物品 背包最大容积 
//v[i]每种物品的体积 w[i]每种物品的价值 s[i]每种物品的件数 
int v[maxn],w[maxn],s[maxn]; 
int f[maxn];//f[j]表示不超过体积j的最大价值 
int main(){
    cin>>N>>V;//输入N种物品和背包最大容积V 
    for(int i=1;i<=N;i++){//输入每种物品的体积 价值 数量 
    	cin>>v[i]>>w[i]>>s[i];	
	}
    for(int i=1;i<=N;i++){//枚举物品1~N件和体积组合
		//计算物品和背包容积0~V整数组合计算所有最大价值(使用i-1,所以滚动数组从后向前)
        for(int j=V;j>=0;j--){//前i种物品最大容积不超过j时计算最大价值 
            //枚举每种物品的件数 
            for(int k=0;k<=s[i];k++){//k从0开始,为0相等于01背包不选的情况 
                if(j>=v[i]*k){//每种逐一放入,放入此种物品件数不能超过背包总容量j 
                	f[j]=max(f[j],f[j-k*v[i]]+w[i]*k);
				}
            }
        }
    }
    cout<<f[V];//装入N种物品背包体积不超过V的最大价值 
    return 0;
}

多重背包2 数据量大

问题描述

现有 N件物品和一个容量为V的背包,每种物品有s[i]件,第 i种物品的体积是 v[i],价值是 w[i],在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值

输入格式

第一行两个整数,N和V用空格隔开,分别表示物品种数和背包容积 ( 0<=n,m<=1000)

接下来有N行每行三个整数,v[i],w[i],s[i],用空格隔开,分别表示第i件物品的体积、价值和每种物品的数量

( 0<=v[i],w[i],s[i]<=2000)

输出格式

输出一个整数,表示最大价值

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例

10

二进制分堆 时间复杂度大大降低

时间复杂度

O(n^2 * logS)

大致思路

1 在二维数组朴素算法 三层循环实现中,如果把第3层循环数大大降低,时间复杂度就会大大降低

2 分堆思路

示例1

7件物品,分堆如下

1     2    4
2^0  2^1  2^2

示例2

8件物品,分堆如下

 1     2    4       1
 2^0  2^1  2^2  8-2^0-2^1-2^2 

示例3

10件物品,分堆如下

 1     2    4       3
 2^0  2^1  2^2  10-2^0-2^1-2^2 

3 分堆后,把每堆看做一件物品的01背包,按01背包动态规划思路解决

4 如下使用01背包的一维数组优化思路

参考程序

#include<iostream>
using namespace std;
const int maxn=1010,maxm=2010;
int N,V;//n件物品 V背包可装物品体积 
//v[i]物品分堆后的每堆体积 w[i]物品分堆后的每堆价值 
int v[maxn],w[maxn];
int f[maxm];//f[j]循环过程种外层i堆物品 体积不超过j的最大价值 
int main(){
    cin>>N>>V;
    int cnt=1;
    for(int i=1;i<=N;i++){//n件物品 
    	//a单件物品的体积 b单件物品的价值 s有几件 
        int a,b,s; 
        cin>>a>>b>>s;
        int k=1;//当前分堆的数量 按二进制权递增1 2 4 8 
        while(k<=s){//如果需要分堆数量k<剩余件数s,继续分 
            v[cnt]=a*k;//k件的体积 
            w[cnt]=b*k;//k件的价值 
            s-=k;//当前物品总件数件数分出去的k件 
            k*=2;//k增加一倍 
            cnt++;//总堆数加1 
        }
        /*
          上面while循环最后一次退出前有2种情况 
		  s=k 这时s件物品正好通过二进制拆分完 
		  s<k 这时s没被分完 while循环结束后,会出现s>0 
		*/ 
        if(s>0){//s>0时分最后一堆 
            v[cnt]=s*a;//剩余s件的体积 
            w[cnt]=s*b;//剩余s件的价值
            //总堆数加1,最后一堆还加1是为下一件物品增加数组下标
            cnt++;//最后一件物品的最后一堆多加了1 所以总堆数为cnt-1 
        }
    }
    /*
	   n件物品分了cnt-1堆 所以准确说是i<cnt 不是i<=cnt 
	  使用i<=cnt对最值结果也不会有影响,结果也是对的 
	*/ 
    for(int i=1;i<cnt;i++){//n件物品分组后可以看作01背包处理 
        for(int j=V;j>=v[i];j--){//按体积从大到小递进 V~v[i] ,j<v[i] f[]下标为负数无意义
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[V]<<endl;//背包不超过体积V的最大价值 
    return 0;
}

CSP初赛复习-28-动态规划-练习题
https://www.cnblogs.com/myeln/articles/17626730.html

posted @ 2023-08-16 22:03  new-code  阅读(37)  评论(0)    收藏  举报