背包问题【01 完全 多重】

目录

0-1背包

空间优化:二维变一维

完全背包

优化:

多重背包


要求恰好装满背包,那么在初始化时除了f[0]为0,其它f[1..V]均设为-∞

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。

初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

0-1背包

问题描述:

N件物品和容积为M的背包。

第i件物品的体积为volume [ i ] ,价值为 worth [ i ]。

每种物品只有一件,可以选择放或者不放。

求解将哪些物品装入背包可使价值总和最大。

提示:

采用滚动数组防止超出内存要求

状态转移方程:

 f[ i ][v] = max{ f[i-1] [v] , f[i-1][ v-volume[i] ] + worth[i] }

前i件物品放入容量V的背包时的最大价值

= max { 前i -1 件物品放入容量V的背包时的最大价值 ,

【不放入第i件物品,总价值不变 】

前i -1 件物品放入容量V- volume[i] 的背包时的最大价值 + worth [ i ] 

【放入第i件物品;那么在放之前的物品是i-1容量是V-volume[i] 价值是f[i-1][ v-volume[i] ]    

}

空间优化:二维变一维

要保证j-v[i]没被算过的,就是需要J逆序,也就是从大的到小的,不然如果从正序开始的话,前面那些小的值已经都改变过了,而大的值会因为小的值被改变过,而经过二次改变。

f[v]  =  max{     f[v]  ,   f [ v-volume[i] ]   +   worth [i]     }

最大价值 = max{ 不加入物品i 的上一个最大价值(容量是v),加入物品 i (容量是v-volume[i ])  }

循环,逆推:

// 0 -1 背包 

#include<iostream>
#include<cmath>
#include<cstring>

using namespace std;

int main()

{
	int n;cin>>n;//N件物品 
	int m;cin>>m;//背包容积M

	int worth[n]; //价值
	int volume[n]; //体积 
	
	int dp[m];//注意初始化 
	memset(dp,0,sizeof(dp));//只求最大价值,初始值全部为0
	
	//输入下标i的价值和体积  从0开始
	for(int i=0;i<n;++i)
	{
		cin>>volume[i]>>worth[i];
	} 

	//滚动数组 取前i件物品
	for(int i=0;i<n;i++)
	{
		for(int v=m;v>=volume[i];--v)//
		{
			//容量V的最大价值 = max{ 容量V之前的最大价值 , 将volume(i)的容积变成新物品的价值 } 
			dp[v] = max( dp[v] , dp[v-volume[i]] + worth[i]);
			cout<<" i:"<<i<<" v: "<<v<<" dp[v]:"<<dp[v]<<endl;
		}
	} 
	
	cout<<dp[m]<<endl;
	return 0;
}

/*
4 5
1 2
2 4
3 4
4 5
 i:0 v: 5 dp[v]:2
 i:0 v: 4 dp[v]:2
 i:0 v: 3 dp[v]:2
 i:0 v: 2 dp[v]:2
 i:0 v: 1 dp[v]:2
 i:1 v: 5 dp[v]:6
 i:1 v: 4 dp[v]:6
 i:1 v: 3 dp[v]:6
 i:1 v: 2 dp[v]:4
 i:2 v: 5 dp[v]:8
 i:2 v: 4 dp[v]:6
 i:2 v: 3 dp[v]:6
 i:3 v: 5 dp[v]:8
 i:3 v: 4 dp[v]:6
8
*/
 

动态规划01:weight和value

#include<iostream>
using namespace std;
//0-1背包
 
//背包的限重为w(w≤W),求前i(1≤i≤n)个物品装包的最优解。
int n=4;//四种物品 背包的限重为w(w≤W),求前i(1≤i≤n)个物品装包的最优解。
int c=5;//背包最大承载重量 
int w[10]={0,2,1,3,2};//重量 
int v[10]={0,12,10,20,15};//价值 
int f[10][10];//f[i][j]背包限重j,前i个物品的最大价值 
int x[10];
//动态规划 
void bag()
{
	//尝试装物品1
	int temp=c;
	if(c>w[1]-1)temp=w[1]-1;
	for(int i=0;i<=temp;i++)
	{
		f[1][i]=0;//背包限重i时,最大价值是0 因为装不进去第一个物品 
	}
	for(int i=w[1];i<=c;i++)
	{
		f[1][i]=v[1];//此时可以把1装进去,最大价值是1的价值 
	}
	//装其他物品 
	for(int i=2;i<=n;i++)
	{
		int jmax=min(w[i-1],c);
		for(int j=0;j<=jmax;j++)
		{
			f[i][j]=f[i-1][j];
			//装不进去物品i 
		}
		for(int j=w[i];i<=c;j++)
		{
			f[i][j]=max(f[i-1][j],(f[i-1][j-w[i]]+v[i]));
		}
	}
	
	for(int i=n;i>1;i--)
	{
		if(f[i][c]==f[i-1][c])
		{
			x[i]=0;
		}
		else
		{
			x[i]=1;
			c=c-w[i];
		}
	}
	x[1]=(f[1][c])?1:0;
} 


int main()
{
	bag();
	return 0;
}

 动态规划02:weight和value

//0-1背包
const int n=4;//物品
const int c=5;//背包总重量 
int v[n]={1,2,3,4};//价值
int w[n]={2,4,4,5};//重量 
 
void bag01()
{
	
	int dp[n+1][c+1]={0};//加入物品i,重量为c时的最大价值
	int x[n]={0};
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=c;j++)//载重1-c 
		{
			//对于当前载重和当前物品,只有两种可能(装得下/装不下)
			//能装下 有种结果(装更优,不装更优) 
			if(j<w[i])//i装不进去 
			{
				dp[i][j]=dp[i-1][j];//需要有dp[0][]因此i从1开始 
			}
			else//i能装进去 
			{
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
			}
		}
	}
	//输出动态规划表 
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=c;j++)
		{
			cout<<"前"<<i<<"个物品载重为"<<j<<"的最大价值:"<<dp[i][j]<<endl;
		}
	}
	//n,c
	int rest=c;//剩余载重量
	for(int i=n-1;i>=0;i--) 
	{
		if(dp[i][rest]==dp[i-1][rest])//没有装第i件 
		{
			x[i]=0; 
		}else
		{
			x[i]=1;
			//剩余重量 
			rest=rest-w[i]; 
		}
	}
	cout<<"最大价值"<<dp[n][c]<<endl; 
	for(int i=0;i<n;i++)
	{
		cout<<x[i]<<'\t';
	}
	cout<<"end"<<endl;;
}

比较:

实质是一样的,

若容量j<w[i]装不下,>=时可以装下。只不过一种方法是在for循环中用if判断。一种是先确定分割点jmax=w[i]-1在进行两个for循环。

一种方法是先将第一个物品放进去,单独的一个初始化。一种方法是设一个0物品的状态,然后遍历时就从1-n个物品,不需要再单独考虑。

完全背包

问题描述:

N种物品,背包M,每种物品都无限件。体积volume[i] 价值worth【i】

总价值最大

关系式:

(1):  dp(i,j)=max(dp(i-1,j) , dp(i-1,j-v)+w , dp(i-1,j-2v)+2w , dp(i-1,j-3v)+3w,...)
(2): dp(i,j-v)=max( dp(i-1,j-v) , dp(i-1,j-2v)+w,dp(i-1,j-3v)+2w , dp(i-1,j-4v)+3w,..)
(2-1):dp(i,j-v)+w=max( dp(i-1,j-v)+w , dp(i-1,j-2v)+2w,dp(i-1,j-3v)+3w , dp(i-1,j-4v)+4w,...)
可以发现  dp(i,j)=max(dp(i-1,j) ,后面的项中的最大值是(2-1)中的dp[i,j-v]+w )

所以:dp(i,j)=max(dp(i-1,j),dp(i,j-v)+w)

// v[i] 表示选择一个 i 物品,dp[i] [j-w[i]] 就已经包含了 k-1 个 i 物品的最大价值, 
// 因此两者组合起来就是 k 个 i 物品使总价值最大。
 

代码:

//可以取多件
for(int i=1;i<=n;i++)//n种物品
{
	for(int v=0 ; v<=m ;++v)//
	{
		dp[i][v]=dp[i-1][v];//继承上一个背包
		if(v>=volume[i])
		{
			dp[i][v] = max( dp[i][v] , dp[i][v-volume[i]] +worth[i]);
//v[i] 表示选择一个 i 物品,dp[i] [j-w[i]] 就已经包含了 k-1 个 i 物品的最大价值, 
//因此两者组合起来就是 k 个 i 物品使总价值最大。

		}
	}
} 

优化:
 

// 完全背包 

#include<iostream>
#include<cmath>
#include<cstring>

using namespace std;

int main()

{
	int n;cin>>n;//N件物品 
	int m;cin>>m;//背包容积M

	int worth[n]; //价值
	int volume[n]; //体积 
	
	int dp[m];//注意初始化 
	memset(dp,0,sizeof(dp));//只求最大价值,初始值全部为0
	
	//输入下标i的价值和体积  从0开始
	for(int i=0;i<n;++i)
	{
		cin>>volume[i]>>worth[i];
	} 

	//可以取多件
	for(int i=0;i<n;i++)//n种物品
	{
		for(int v=volume[i]; v<=m ;++v)//
		{
			if(v>=volume[i])
			{
				dp[v] = max( dp[v] , dp[v-volume[i]] +worth[i]);
				
			}
		}
	} 
	cout<<dp[m]<<endl;
	return 0;
}

/*
4 5
1 2
2 4
3 4
4 5
10
*/
 

多重背包

N种物品,背包容量V,

第i中物品最多si件,体积volume,价值worth。

求最大价值

// 多重背包 

#include<iostream>
#include<cmath>
#include<cstring>

using namespace std;

int main()

{
	int n;cin>>n;//N件物品 
	int m;cin>>m;//背包容积M

	int worth[n]; //价值
	int volume[n]; //体积 
	int s[n];//数量
	 
	int dp[m];//注意初始化 
	memset(dp,0,sizeof(dp));//只求最大价值,初始值全部为0
	
	//输入下标i的价值和体积  从0开始
	for(int i=0;i<n;++i)
	{
		cin>>volume[i]>>worth[i]>>s[i];
	} 

	for(int i=0;i<n;i++)
	{
		for(int v=m;v>=0;v--)
		{
			for(int k=0;v>=k*volume[i] && k<=s[i];k++)
			{
				dp[v]= max( dp[v] , dp[v-k*volume[i] ] + k* worth[i] ) ;
			}
		}
	}
	
	cout<<dp[m]<<endl;
	return 0;
}

/*
4 5
1 2 3
2 4 1
3 4 3
4 5 2
10
*/
 

因为每组的物品的个数都不一样,没有数列求和一类的公式。

更新次序的内部关系:

 f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , ...,f[i-1][j-sv] + sw) 
 f[i , j-v]= max(     f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , ...,,f[i-1][j-(s+1)v] + sw) 

max无法做减法,所以不可以用完全背包方式优化。

二进制优化的方法:


假设s = 1023(2^10-1)
我们可以用1,2,4,8,…,512 (2^9) 来组合成0~1023,枚举10次。
即2 ^0 , 2 ^1 , . . . . , 2^ k  可以表示所有 0 -2^{k+1}里面的数

总结:
给我们S个物品,我们可以拆分打包成logS物品,就变成01背包问题。
即1 , 2 , 4 , 8 , . . . , 2 k , C ( C = S − s u m ( 1 , 2^ k ) < 2 ^{k + 1} 
拼成0~2^(k+1)
 

// 多重背包 

#include<iostream>
#include<cmath>
#include<cstring>

using namespace std;

int main()

{
	int n;cin>>n;//N件物品 
	int m;cin>>m;//背包容积M

	int worth[n]; //价值
	int volume[n]; //体积 
	int s[n];//数量
	 
	int dp[m];//注意初始化 
	memset(dp,0,sizeof(dp));//只求最大价值,初始值全部为0
	
	//输入下标i的价值和体积  从0开始
	int cnt=0;//种类数
	int vv,ww,ss; 
	for(int i=0;i<n;++i)
	{
		cin>>vv>>ww>>ss;//当前物品的体积,价值,个数
//把它拆分成1 2 4 ...2^x ..余数 个物品,分别赋予相应的体积和价值
//就像是2个物品组成新的物品,赋予新的名字
		//cin>>volume[i]>>worth[i]>>s[i];
		int k=1;
		while(k<=ss)
		{
			volume[cnt]=vv*k;//第cnt种物品是由k个当前物品组成的,它的容积是k*vv;
			worth[cnt]=ww*k;
			cnt++;//种类数加一
			ss-=k;//个数
			k*=2; 
			
		}
		if(ss>0)
		{
			volume[cnt]=vv*ss;
			worth[cnt]=ww*ss;
			cnt++;
		}
		
	} 

	for(int i=0;i<=cnt;i++)
	{
		for(int v=m;v>=volume[i];v--)
		{
				dp[v]= max( dp[v] , dp[v-volume[i] ] +  worth[i] ) ;
		}
	}
	
	cout<<dp[m]<<endl;
	return 0;
}

/*
4 5
1 2 3
2 4 1
3 4 3
4 5 2
10
*/
 

分组背包问题:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int v[N],w[N],dp[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int s;
        cin>>s;
        for(int j=1;j<=s;j++)
            cin>>v[j]>>w[j];
        for(int kk=m;kk>=1;kk--)
            for(int k=1;k<=s;k++)
            {   
                if(kk>=v[k])
                    dp[kk]=max(dp[kk],dp[kk-v[k]]+w[k]);
            }
    }
    cout<<dp[m];
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值