背包九讲专题
01背包问题
https://www.acwing.com/problem/content/2/
最基础做法
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int w[1005],v[1005];
int dp[1005][1005];//前i个物品使用了j体积的最大价值
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
dp[0][0]=0;
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
if(j>=v[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
else
dp[i][j]=dp[i-1][j];
}
}
int res=0;
for(int i=0;i<=V;i++) res=max(res,dp[N][i]);
cout<<res;
return 0;
}
// freopen("testdata.in", "r", stdin);
优化版
去掉了一维情况,同时通过逆方向j的循环,保证算DP[j]时 DP[j-v[i]]没有算过
由于全局变量全部初始化为0了,dp[V]表示小于等于该体积时的最大价值,故可以直接为答案
可以这样理解f[0]=0 f[v]=0 而最终答案一定保证了体积在V这个范围
故其转移方程会一样
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int w[1005],v[1005];
int dp[1005];
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=V;j>=v[i];j--){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
完全背包问题
https://www.acwing.com/problem/content/3/
这里的思想很精髓,没有向上面将j倒着循环。这样在计算dp[j]时保证dp[j-v[i]]被计算过,即前面的最优解是满足可以选择无数次同一个物品条件
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int w[1005],v[1005];
int dp[1005];
int main(){
cin>>N>>V;
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<=V;j++){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
多重背包问题
https://www.acwing.com/problem/content/4/
正常做法 o(n3)
和01背包比多了一维来循环使用几次
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int dp[105];
int v[105],w[105],s[105];
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i]>>s[i];
}
for(int i=1;i<=N;i++){
for(int j=V;j>=v[i];j--){
for(int k=1;k<=s[i]&&k*v[i]<=j;k++){
dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
}
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
优化:
假设题目里有体积v价值w的物品两件,那么我们可以吧他们看成两个单一的物品。
进行这样的转化后问题本质就是01背包
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int dp[1005];
int v[10005],w[10005];
int nv,nw,ns;
int temp=1;
int main(){
cin>>N>>V;
while(N--){
cin>>nv>>nw>>ns;
v[temp]=nv;
w[temp++]=nw;//无论如何都起码有一个物品,直接保存进去
if(ns>1){//如果还有多的物品,单独保存起来
for(int i=2;i<=ns;i++){
v[temp]=nv;
w[temp++]=nw;
}
}
}
//进行转化后就是01背包了
for(int i=1;i<=temp;i++){
for(int j=V;j>=v[i];j--){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
进一步优化版
https://www.acwing.com/problem/content/5/
前面的思路是吧多的物品都一个个看成单一的一份,我们可以通过二进制优化使得不需要全部分出来。
假设我有物品7个,最少需要分多少组出来可以表示0-7任何数字?
答案是1,2,4 分成这3组后0-7所有数字都可以表示出来。
同时如果数字是10?我们只需要多一组3就可以和上面完全一样了。
计算的方法就是让数字n不断减一个从1开始不断乘2的数字
最后减不了:如10最后是3-8 就直接吧3另开一组。
通过二进制分组后时间复杂度会大幅度减低。
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
struct Good{
int v,w;
};
vector<Good> goods;
int v,w,s;
int dp[2005];
int main(){
cin>>N>>V;
while(N--){
cin>>v>>w>>s;
for(int k=1;k<=s;k*=2){
s-=k;
//不断分组
goods.push_back({k*v,k*w});
}
if(s>0){
//减不了s还有剩余就直接另当一组
goods.push_back({s*v,s*w});
}
}
//分组完毕后就是01背包了
for(auto g:goods){
for(int j=V;j>=g.v;j--){
dp[j]=max(dp[j],dp[j-g.v]+g.w);
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
混合背包问题
https://www.acwing.com/problem/content/7/
多重背包转化为01背包,然后根据是01背包还是完全背包去选择j的循环就O了
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
int dp[1005];
struct Good{
int s,v,w;
};
vector<Good> goods;
int nv,nw,ns;
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>nv>>nw>>ns;
if(ns<=0){
goods.push_back({ns,nv,nw});
}
else {
for(int k=1;k<=ns;k*=2){
ns-=k;
goods.push_back({-1,k*nv,k*nw});
}
if(ns>0){
goods.push_back({-1,ns*nv,ns*nw});
}
}
}
for(auto g:goods){
if(g.s==-1){
for(int j=V;j>=g.v;j--){
dp[j]=max(dp[j],dp[j-g.v]+g.w);
}
}
else {
for(int j=g.v;j<=V;j++){
dp[j]=max(dp[j],dp[j-g.v]+g.w);
}
}
}
cout<<dp[V];
return 0;
}
// freopen("testdata.in", "r", stdin);
二维费用的背包问题
https://www.acwing.com/problem/content/8/
扩展成二维即可
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V,M;
int v[1005],m[1005],w[1005];
int dp[1005][1005];//用了j体积,k重量的最大价值
int main(){
cin>>N>>V>>M;
for(int i=1;i<=N;i++){
cin>>v[i]>>m[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=V;j>=v[i];j--){
for(int k=M;k>=m[i];k--){
dp[j][k]=max(dp[j][k],dp[j-v[i]][k-m[i]]+w[i]);
}
}
}
cout<<dp[V][M];
return 0;
}
// freopen("testdata.in", "r", stdin);
分组背包问题
https://www.acwing.com/problem/content/9/
需要额外一个循环去循环每个组物品的选择 无法优化
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int n,m;
const int N=105;
int dp[N],v[N],w[N];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int s;
cin>>s;
for(int j=0;j<s;j++){
cin>>v[j]>>w[j];
}
for(int j=m;j>=0;j--)
{
for(int k=0;k<s;k++)
{ if(j>=v[k])
dp[j]=max(dp[j],dp[j-v[k]]+w[k]);
}
}
}
cout<<dp[m];
return 0;
}
// freopen("testdata.in", "r", stdin);
背包问题求方案数
https://www.acwing.com/problem/content/11/
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
int N,V;
const int mod=1e9+7,INF=1e5;
int v[1005],w[1005],f[1005],dp[1005];
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=V;i++){
dp[i]=-INF;
}
f[0]=1;
for(int i=1;i<=N;i++){
for(int j=V;j>=v[i];j--){
int temp=max(dp[j],dp[j-v[i]]+w[i]);
int s=0;
//进行判断,对选择的情况加方案数
//同时这里是有可能出现两种选择都是最优解情况
if(temp==dp[j]){
s+=f[j];
}
if(temp==dp[j-v[i]]+w[i]){
s+=f[j-v[i]];
}
if(s>=mod) s-=mod;
dp[j]=temp;
f[j]=s;
}
}
//找到最优解对应的体积
int Maxv=0;
for(int i=0;i<=V;i++){
Maxv=max(Maxv,dp[i]);
}
int res=0;
for(int i=0;i<=V;i++){
if(dp[i]==Maxv){
res+=f[i];
if(res>=mod) res-=mod;
}
}
cout<<res<<endl;
return 0;
}
// freopen("testdata.in", "r", stdin);
背包问题求具体方案
https://www.acwing.com/problem/content/12/
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N],f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=n;i>=1;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]);
}
}
}
int vol=m;
for(int i=1;i<=n;i++){
if(i==n&&vol>=v[i]){
cout<<i<<" ";
break;
}
if(vol <= 0)
break;
if(vol-v[i]>=0&&f[i][vol]==f[i+1][vol-v[i]]+w[i]){
cout<<i<<" ";
vol-=v[i];
}
}
return 0;
}
// freopen("testdata.in", "r", stdin);

浙公网安备 33010602011771号