目录
要求恰好装满背包,那么在初始化时除了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;
}