1. 01背包
题目描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
测试样例:`
输入:
4 5
1 2
2 4
3 4
4 5
输出:
8
二维
#include<iostream>
using namespace std;
int n,m;
int f[1010][1010];//前i个物品中,体积为j的最大价值
int v[1010],w[1010];//int v,w;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d %d",&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]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
对二维转一维的理解:
二维转一维的关键问题在于如何将f[i-1][j-v[i]]转化成f[j-v[i]],因为直接去掉的话,f[j-v[i]]其实就是f[i][j-v[i]](内层正序遍历),而f[i][j-v[i]]表示前i个背包中选第i个背包,体积为j-v[i]的价值,因为正序遍历,所以,在第i层中,f[i][j-v[i]]的状态已经被算过,而如果此时恰好f[j-v[i]]大于f[j],就会产生w[i]被计算过两次的情况 所以我们应该让f[i-1][j-v[i]]与f[j]比较,而不是让f[i][j-v[i]]与f[j]比较 为了实现将f[j-v[i]]转化成f[i-1][j-v[i]],内层for循环选择倒叙遍历,因为j>j-v[i],所以遍历到j时,第i层f[j-v[i]]还没有被赋值,此时f[j-v[i]]的值还是第i-1层赋给的,从而实现了f[j-v[i]]表示f[i-1][j-v[i]]的效果
一维
#include<iostream>
using namespace std;
int n,m;
int f[1010];//体积为j时的最大价值
int v,w;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
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;
}
对样例的解释(初次理解)
2. 完全背包
三维转二维优化:
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][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][j]=max(f[i][j-v]+w , f[i-1][j])=max(f[i][j], f[i][j-v]+w)
比较01背包与完全背包:
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
#include<iostream>
using namespace std;
int f[1010];
int v[1010],w[1010];
int n,m;
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=v[i];j<=m;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
3. 多重背包
输入:
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出:
10
暴力做法
#include<iostream>
using namespace std;
int n,m;
int f[110];//体积为j时的最大价值
int main()
{
cin>>n>>m;
int v,w,s;
for(int i=0;i<n;i++)
{
cin>>v>>w>>s;
for(int j=m;j>=0;j--)
{
for(int k=1;k<=s;k++)
{
if(k*v>j) break;
f[j]=max(f[j],f[j-k*v]+k*w);
}
}
}
cout<<f[m]<<endl;
return 0;
}
对二进制优化的理解:
要真正理解这个问题,首先要明白dp做法的本质,对于01背包来说,dp做法会不重不漏筛选每一个背包,最后找到最大价值。因此,这个问题,可以用二进制做法,重新对背包进行定义
二进制优化
#include<iostream>
#include<vector>
using namespace std;
struct good
{
int v,w;
};
int n,m;
int f[2010];
vector<good> goods;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
int v,w,s;
cin>>v>>w>>s;
for(int k=1;k<=s;k=k*2)
{
s=s-k;
goods.push_back({v*k,w*k});
}
if(s>0) goods.push_back({v*s,w*s});//最后一个可能不是2的正次幂
}
for(auto g:goods)
{
for(int j=m;j>=g.v;j--)
{
f[j]=max(f[j],f[j-g.v]+g.w);
}
}
cout<<f[m]<<endl;
return 0;
}
4. 二维费用背包
类型1:求最大值
给出怪物的数量n,人物血量H,人物的攻击S;
接下来n行,给出每只怪物的血量h,攻击s,价值w;
每消灭一个怪物,消耗人物h的血量和s的攻击,S如果为负数,需要用H去弥补S,如果H为负数则结束;
输出可以获得的最大价值;
#include<iostream>
using namespace std;
int n,H,S;
long long dp[310][310];//消耗血量为i、攻击为j的最大价值
int main()
{
cin >> n >> H >> S;
int ans = 0;
for(int i = 1; i <= n; i ++)
{
int h,s,w;
cin >> h >> s >> w;
for(int j = H; j > h; j --)
{
for(int k = S; k >= 0; k --)
{
if(j + k > h + s && j > h)//第一个条件保证攻击不够时,血量可以补完
{
if(s > k)//攻击不够,血量补
{
int x = h + s - k;
dp[j][k] = max(dp[j][k],dp[j - x][0] + w);
}
else//攻击够
{
dp[j][k] = max(dp[j][k],dp[j - h][k - s] + w);
}
}
}
}
}
cout << dp[H][S] << endl;
return 0;
}
类型2:求方案数
问存在多少种序列A,满足如下的条件:
- 长度为n
- A[i] <= m
- ∑ (1-n)A[i]<=K
#include<iostream>
using namespace std;
int n,m,K;
long long f[55][3050];//当前数字个数j,值不超过k的方案数
int main()
{
cin>>n>>m>>K;//n为数字个数,m为每个数字的取值范围,相加之和不能大于K
f[0][0]=1;
for(int j=1;j<=n;j++)
{
for(int k=1;k<=K;k++)
{
for(int i=1;i<=m;i++)//最内层遍历每一个数的可能的取值范围
{
if(k>=i) f[j][k]=(f[j][k]+f[j-1][k-i])%998244353;
}
}
}
long long ans=0;
for(int i=1;i<=K;i++)
{
ans=(ans+f[n][i])%998244353;
}
cout<<ans<<endl;
return 0;
}