
0-1背包问题
思路1:二维动态规划法
对于状态f[i] [j]定义为前i给物品下,背包重量为j的最优解:
当前的状态依赖于之前的状态,那么初始的状态f[0] [0]==0开始决策。有n个物品,则需要N种决策,每一次对第i件物品的决策,状态f[i] [j]不断由之前的状态跟新而来。
对于第i件物品时,如果空间的容量不够, 那么f[i] [j]==f[i-1] [j]
如果背包的容量够用,那么就有两种情况:
如果选择了第i件物品,那么f[i] [j]==f[i-1] [j-v[i]]]+w[i]。
如果没有选择第i件物品,那么 f[i] [j]==f[i-1] [j]
那么最后的情况是两者的max()
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//传入数量和体积
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
//如果体积不够用
if(j<v[i])
f[i][j]=f[i-1][j];
//如果体积够用
else
{
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
}
int res=0;
for(int j=0;j<=m;j++) res=max(res,f[n][j]);
cout<<res<<endl;
return 0;
}
优化:一维将状态f[i] [j]优化为一维f[j],定义为:N件物品,背包容量为j的最优解。
枚举的顺序必须是从最大容量m开始
在二维的情况下,状态f[i] [j]是由上一轮的i-1的状态得来的
为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i] [j]是由上一轮i - 1的状态得来的,f[i] [j]与f[i - 1] [j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
例如,一维状态第i轮对体积为 33 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f [i - 1] [4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i] [4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1] [4]。
简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。
状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] 。
for(int i = 1; i <= n; i++)
for(int j = m; j >= 0; j--)
{
if(j < v[i])
f[i][j] = f[i - 1][j]; // 优化前
f[j] = f[j]; // 优化后,该行自动成立,可省略。
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); //优化前
f[j] = max(f[j], f[j - v[i]] + w[i]); // 优化后
}
for(int i=1;i<=n;i++)
{
for(int j=m;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
完整代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
{
f[j]=max(f[j],f[j-v]+w);
}
}
cout<<f[m]<<endl;
return 0;
}
完全背包问题
思路:把完全背吧问题转化为多个0-1背包问题
#include<iostream>
#include<cstring>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
//传入物品的种类和和背包的容量
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
for(int k=0;j>=k*v[i];k++)
{
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
不出意外,该算法Time Limit Exceeded ,现在有的办法是对该算法进行将维处理。降低算法的循环次数。
- f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2v]+2w , f[i-1,j-3v]+3w , …)
- f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2v] + w , f[i-1,j-3v]+2*w , …)
由上两式,可得出如下递推关系:
f[i] [j]=max(f[i,j-v]+w , f[i-1] [j])- 对上面的代码进行改造
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
if(j>=v[i])
{
f[i][j]=max(f[i][j-v]+w,f[i-1][j]);
}
}
}
完整代码
include<iostream>
#include<cstring>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
//传入物品的种类和和背包的容量
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
//如果没有足够的容量放入
f[i][j]=f[i-1][j];
if(j-v[i]>=0)
f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
进一步优化
逆序是为了保证更新当前状态时,用到的状态是上一轮的状态,保证每个物品只有一次或零次;在这里,因为每个物品可以取任意多次,所以不再强求用上一轮的状态,即本轮放过的物品,在后面还可以再放;
思路模拟:
首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入
v[1] = 1, w[1] = 2
v[2] = 2, w[2] = 4
v[3] = 3, w[3] = 4
v[4] = 4, w[4] = 5i = 1 时: j从v[1]到5
dp[1] = max(dp[1],dp[0]+w[1]) = w[1] = 2 (用了一件物品1)
dp[2] = max(dp[2],dp[1]+w[1]) = w[1] + w[1] = 4(用了两件物品1)
dp[3] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] = 6(用了三件物品1)
dp[4] = max(dp[4],dp[3]+w[1]) = w[1] + w[1] + w[1] + w[1] = 8(用了四件物品1)
dp[5] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] + w[1] + w[1] = 10(用了五件物品)i = 2 时:j从v[2]到5
dp[2] = max(dp[2],dp[0]+w[2]) = w[1] + w[1] = w[2] = 4(用了两件物品1或者一件物品2)
dp[3] = max(dp[3],dp[1]+w[2]) = 3 * w[1] = w[1] + w[2] = 6(用了三件物品1,或者一件物品1和一件物品2)
dp[4] = max(dp[4],dp[2]+w[2]) = 4 * w[1] = dp[2] + w[2] = 8(用了四件物品1或者,两件物品1和一件物品2或两件物品2)
dp[5] = max(dp[5],dp[3]+w[2]) = 5 * w[1] = dp[3] + w[2] = 10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2)i = 3时:j从v[3]到5
dp[3] = max(dp[3],dp[0]+w[3]) = dp[3] = 6 # 保持第二轮的状态
dp[4] = max(dp[4],dp[1]+w[3]) = dp[4] = 8 # 保持第二轮的状态
dp[5] = max(dp[5],dp[2]+w[3]) = dp[4] = 10 # 保持第二轮的状态i = 4时:j从v[4]到5
dp[4] = max(dp[4],dp[0]+w[4]) = dp[4] = 10 # 保持第三轮的状态
dp[5] = max(dp[5],dp[1]+w[4]) = dp[5] = 10 # 保持第三轮的状态
#include<iostream>
#include<cstring>
using namespace std;
const int N=1010;
int n,m;
int f[N];
int v[N],w[N];
int main()
{
//传入物品的种类和和背包的容量
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
//如果没有足够的容量放入
f[j]=f[j];
if(j-v[i]>=0)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
多重背包问题一
思路一:帮多重背包问题强行拆为0-1背包问题
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
//分别保存体积和价值
int v[10010],w[10010],t=0;
int dp[10010];
int main()
{
cin>>n>>m;
while(n--)
{
int v,w,s;
cin>>v>>w>>s;
while(s--)
{
//拆开多重背包问题
a[++t]=v;
b[t]=w;
}
}
//解决0-1背包问题
for(int i=1;i<=t;i++)
{
for(int j=m;j>a[i];j--)
{
dp[j]=max(dp[j],dp[j-a[i]+b[i]]);
}
}
cout<<dp[m]<<endl;
return 0;
}
复杂度有点高,对代码进行优化
#include<iostream>
#include<cstring>
using namespace std;
int dp[1010],n,t,v,w,s;
int main()
{
cin>>n>>t;
while(n--)
{
cin>>v>>w>>s;
for(int i=1;i<=s;i++)
{
for(int j=t;j>=w;j--)
{
dp[j]=max(dp[j],dp[j-w]+w);
}
}
}
cout<<dp[t]<<endl;
return 0;
}
思路二
#include<iostream>
#include<cstring>
#include<algorithm>
const int N=110;
int n,m;
int f[N];
using namespace std;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int v,w,s;
cin>>v>>w>>s;
for(int j=m;j>=0;j--)
{
for(int k=1;k<=s&&k*v<=j;k++)
{
f[j]=max(f[j],f[j-k*v]+k*w);
}
}
}
cout <<f[m]<<endl;
return 0;
}
多重背包问题二
二进制优化:对于任何一个数s,最少有[logs]个数可以表示0~s的所有整数。
比如对于7,我们有3个数:1,2,4(2的0次方,2的1次方,2的平方)可以表示0~7所有的数。
0=0; 1=1;2=2;3=1+2;4=4;5=1+4;6=2+4;7=1+2+4;
再不如10,我们有[log2 10]=4个数能表示010所有的数;010=(0~7)+3;
所有数分别为:1,2,4,3;
0=0; 1=1;2=2;3=3;4=4;5=1+4;6=2+4;7=1+2+4;8=1+3+4;
9=2+3+4;10=1+2+4+3
对于这个题:
我们可以把多重背包问题按照二进制转换为N*[log2 k]个0-1背包问题
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2010;
int n,m;
int f[N];
struct good
{
int v;
int w;//分别表示体积和价格
};
int main()
{
vector<good>goods;
cin>>n>>m;
for(int i=0;i<n;i++)
{
int v,w,s;
cin>>v>>w>>s;
//把多重背包问题按照二进制转换为多个0-1背包问题
for(int k=1;k<=s;k*=2)
{
s-=k;
goods.push_back({v*k,w*k});
}
if(s>0) goods.push_back({v*s,w*s});
}
for(auto good:goods)
for(int j=m;j>=good.v;j--)
{
f[j]=max(f[j],f[j-good.v]+good.w);
}
cout<<f[m]<<endl;
return 0;
}