算法笔记 11.7 背包问题

01背包问题:
    有n件物品,每件物品的重量为w[i],价值为c[i]。现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内
物品的总价值最大。其中每种物品都只有1件。

样例:
5 8 //n=5 V=8   5件物品  8大的包
3 5 1 2 2 //w[i] 每件物品的重量  w[i]之和有上限
4 5 2 1 3 //c[i] 每件物品的价值 要求价值之和尽可能大  求最大

取5+1+2=8   最大价值5+2+3=10

 


令:dp[i][v]表示前i件物品(1<=i<=n,0<=v<=V)恰好装入容量为v的背包中所能获得的最大价值
dp[i][v]={
    dp[i-1][v]            //不放第i件物品
    dp[i-1][v-w[i]]+c[i]  //放第i件物品,转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值
}

 

1.普通二维数组形式

#include
#include
#include
using namespace std;


const int N=1000;
const int V=10000;
int w[N],c[N];//重量  价值 
int dp[N][V];


int main(){
	int n,V;
	while(cin>>n>>V){
		for(int i=1;i<=n;i++){
			cin>>w[i]; //重量 
		} 
		
		for(int i=1;i<=n;i++){
			cin>>c[i];//价值 
		} 
		memset(dp,0,sizeof(dp));
		
		for(int i=1;i<=n;i++){
			for(int v=1;v<=V;v++){
				if(v

算法笔记 11.7 背包问题_第1张图片

注意:状态转移时直接写:

for(int i=1;i<=n;i++){
	for(int v=w[i];v<=V;v++){
		dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);
	}
} 

是错误的,第二组数据即可测出来,后面的dp[i][v]需要前面的的dp[i-1][v-w[i]]  当w[i]变小了后(7<9且7<8) dp[i-1][v-w[i]]第一维v-w[i]会变大,从w[i]~V枚举 前面的0~w[i]会一直保持为0,一旦后面的w[i]变小了,产生了新的0~w[i]最小区间,就可能出错 即是逆序也不行  二维的还必须写if..else

很明显二维数组形式好理解,但是定义二维数组时总容量有多大,第二维就多大,浪费空间,很多时候根本开不了那么大的数组。于是有了下面第二种更常见的写法!

 

2.一维滚动数组

不需要记录所有之前的dp[i][v]状态  每次只需要知道上一次dp[i-1][v]的所有v取值状态即可   其实是dp[i-1][v]和dp[i-1][v-w[i]]两个状态。

	for(int i=1;i<=n;i++){
			for(int v=V;v>=w[i];v--){
				dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
			}
		} 

式子中dp[v]=dp[v]=max(dp[v],dp[v-w[i]]+c[i]);  max()内的dp[v]其实是上次的dp[v]即dp[i-1][v]。

必须逆序枚举是因为求dp[v]时可能需要上一次的dp[v-w[i]],如若正序枚举,那么上次的dp[v-w[i]]被提前更新覆盖了。

参考博文

#include
#include
#include
using namespace std;

const int N=1000;
const int V=10000;
int w[N],c[N];//重量  价值 
int dp[V];

int main(){
	int n,V;
	while(cin>>n>>V){
		for(int i=1;i<=n;i++){
			cin>>w[i]; //重量 
		}
		for(int i=1;i<=n;i++){
			cin>>c[i];//价值 
		} 
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			for(int v=V;v>=w[i];v--){
				dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v]  等号后的dp[v]相当于dp[i-1][v] 
			}
		}
		cout<

算法笔记 11.7 背包问题_第2张图片

 

 

二.完全背包

    有n种物品,每种物品的单件重量为w[i],价值为c[i].现在有一个容量为V的背包,问如何选取物品放入背包,使得背包内
物品的总价值最大。其中每种物品都有无穷件。

令:dp[i][v]={
    dp[i-1][v]  //不放第i件物品
    dp[i][v-w[i]]+c[i] //只是是i不是i-1  每件物品可以放多次 转移到dp[i][v-w[i]]的状态
}
 

二维形式:
和01背包一样,仅仅把i-1改成i
for(int i=1;i<=n;i++){
    for(int v=1;v<=V;v++){
        if(v             dp[i][v]=dp[i-1][v];//从小到大枚举  边界0也初始化了  前面的肯定都已经知道了 
        }else{
            dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
        }
    }

一维形式: 和01背包一样,v的枚举顺序变了,
正序枚举 因为需要的是dp[i-1][v]和dp[i][v-w[i]],  dp[i-1][v]没什么,每次都是max内的dp[v]都是dp[i-1][v]
但是正序枚举会在求dp[v]之前提前更新dp[i][v-w[i]], 此时需要的就是dp[i][v-w[i]]而非dp[i-1][v-w[i]]
for(int i=1;i<=n;i++){
    for(int v=w[i];v<=V;v++){
        dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v]  等号后的dp[v]相当于dp[i-1][v] 
    }
}
 

#include
#include
#include
using namespace std;

const int N=1000;
const int V=10000;
int w[N],c[N];//重量  价值 
int dp[V];
//完全背包 
int main(){
	int n,V;
	while(cin>>n>>V){
		for(int i=1;i<=n;i++){
			cin>>w[i]; //重量 
		}
		for(int i=1;i<=n;i++){
			cin>>c[i];//价值 
		} 
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++){
			for(int v=w[i];v<=V;v++){//正序 
				dp[v]=max(dp[v],dp[v-w[i]]+c[i]);//每次等号前的dp[v]相当于dp[i][v] ?等号后的dp[v]相当于dp[i-1][v]?
			}
		}
		cout<

算法笔记 11.7 背包问题_第3张图片

 

 

你可能感兴趣的:(算法竞赛,算法笔记,DP)