背包九讲专题

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);
posted @ 2021-03-12 21:09  一个经常掉线的人  阅读(57)  评论(0)    收藏  举报