2023.2.9打卡——背包问题

题目来源

2. 01背包问题 - AcWing题库

01背包

朴素算法就是列状态转移,对于第i个物品选或不选,不选则最大值是前i-1个物品的最大值,选则体积减去对应物品体积,价值取两种可能情况的最大值,即

dp[i][j]=max(dp[i-1][j],dp[i-1][j-vi[i]]+wi[i])

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int vi[1001],wi[1001];
int dp[1001][1001];
int main()
{
    int N,V;
    cin>>N>>V;
    for (int i = 1; i <= N; i ++ ){
        cin>>vi[i]>>wi[i];
    }
    for(int i=1;i<=N;i++){
        for(int j=0;j<=V;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=vi[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-vi[i]]+wi[i]);
        }
    }
    cout<<dp[N][V]<<endl;
}

 考虑到只与i-1的状态有关,利用滚动数组;j和j-v[i]变量只有1个数,所以从大到小遍历,这样的话j-vi[i]的值不会被提前更新,还是i-1时的值

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int vi[1001],wi[1001];
int dp[1001];
int main()
{
    int N,V;
    cin>>N>>V;
    for (int i = 1; i <= N; i ++ ){
        cin>>vi[i]>>wi[i];
    }
    for(int i=1;i<=N;i++){
        for(int j=V;j>=vi[i];j--){
            dp[j]=max(dp[j],dp[j-vi[i]]+wi[i]);
        }
    }
    cout<<dp[V]<<endl;
}

完全背包

完全背包与01背包不同之处在于相同物品可以选无限多次,所以状态转移方程为

dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w,dp[i-1][j-2v]+2w+……);

稍作换元

dp[i][j-v]=max(dp[i-1][j-v],dp[i-1][j-2v]+w,dp[i-1][j-3v]+2w+……)+w;

所以可得

dp[i][j]=max(dp[i-1][j],dp[i][j-v]+w); 

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int vi[1001],wi[1001];
int dp[1001];
int main()
{
    int N,V;
    cin>>N>>V;
    for (int i = 1; i <= N; i ++ ){
        cin>>vi[i]>>wi[i];
    }
    for(int i=1;i<=N;i++){
        for(int j=vi[i];j<=V;j++){
            dp[j]=max(dp[j],dp[j-vi[i]]+wi[i]);
        }
    }
    cout<<dp[V]<<endl;
}

多重背包I

与01背包区别在于每个物品可以最多选s个,所以在数据范围在合理时,我们可以直接使用暴力遍历求解,状态转移方程如下:

dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w,dp[i-1][j-2v]+2w+……);

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[101];
int main()
{
    int N,V;
    cin>>N>>V;
    int v,w,s;
    for (int i = 1; i <= N; i ++ ){
        cin>>v>>w>>s;
        for (int j = V; j >= v; j -- ){
            for(int k=0;k<=s && k*v<=j;k++){
                dp[j]=max(dp[j],dp[j-k*v]+k*w);
            }
        }
    }
    cout<<dp[V]<<endl;
}

多重背包II

如果数据量稍微大一点,可以采用二进制表示状态方法进行优化,例如表示1-7,我们事实上只需要1,2,4这3个数排列组合就可以得到,所以捆绑好,对这3个数选或者不选,实现最终的所以状态转移方程为:

dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w);

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int dp[2001];
struct Good{
    int v,w;  
};
int main()
{
    int N,V;
    cin>>N>>V;
    int v,w,s;
    vector<Good> goods;
    for (int i = 1; i <= N; i ++ ){
        cin>>v>>w>>s;
        for (int k=1;k<=s;k*=2){
            s-=k;
            goods.push_back({k*v,k*w});
        }
        if(s>0) goods.push_back({s*v,s*w});
    }
    for (auto good:goods){
        for (int j = V; j >=good.v; j -- ){
            dp[j]=max(dp[j],dp[j-good.v]+good.w);
        }
    }
    cout<<dp[V]<<endl;
}

多重背包III

AcWing 6. 多重背包问题 III【单调队列优化+图示】 - AcWing

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20001;
int dp[N];
int q[N],pre[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for (int i = 0; i < n; i ++ ){
        int v,w,s;
        cin>>v>>w>>s;
        memcpy(pre,dp,sizeof(dp));
        for (int j = 0; j < v ; j ++ ){
            int head=0,tail=-1;
            for(int k=j;k<=m;k+=v){
                if(head<=tail && k-q[head]>s*v) head++;//如果队列长度大了,头++
                while(head<=tail && pre[q[tail]]-(q[tail]-j)/v*w<=pre[k]-(k-j)/v*w) tail--;//如果队列末尾数值大小不大于j+k*v对应的数,更新
                if(head<=tail) dp[k]=max(dp[k],pre[q[head]]+(k-q[head])/v*w);//对k作状态转移,得到下标为j+k*v对应的最大值
                q[++tail]=k;//新加入队列
            }
        }
    }
    cout<<dp[m]<<endl;
}

混合背包

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int dp[1001];
struct Good{
  int v,w;  
};
int main()
{
    int n,m;
    cin>>n>>m;
    for (int i = 0; i < n; i ++ ){
        int v,w,s;
        cin>>v>>w>>s;
        if(s==-1){
            for (int j = m; j >= v ; j -- ){
                dp[j]=max(dp[j],dp[j-v]+w);
            }
        }else if(s==0){
            for (int j = v; j <= m ; j ++ ){
                dp[j]=max(dp[j],dp[j-v]+w);
            }            
        }else if(s>0){
            vector<Good> goods;
            for(int k=1;k<=s;k*=2){
                s-=k;
                goods.push_back({v*k,w*k});
            }
            if(s>0) goods.push_back({s*v,s*w});
            for(auto good:goods){
                for(int j=m;j>=good.v;j--){
                    dp[j]=max(dp[j],dp[j-good.v]+good.w);
                }
            }
        }
    }
    cout << dp[m]<<endl;
}

二维背包费用

与01背包类似,只是多加一维限制,状态转移方程为:

dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-vi[i]][k-mi[i]]+wi[i])

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[101][101];
int main()
{
    int N,V,M;
    cin>>N>>V>>M;
    int v,w,m;
    for (int i = 1; i <= N; i ++ ){
        cin>>v>>m>>w;
        for(int j=V;j>=v;j--){
            for(int k=M;k>=m;k--){
                dp[j][k]=max(dp[j][k],dp[j-v][k-m]+w);
            }
        }
    }
    cout<<dp[V][M]<<endl;
}

分组背包

相当于在组内的值并列进入动态规划中,因为是同一轮判断的,所以对应一个j不会出现组内值同时出现的情况。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 110;
int s[maxn],v[maxn][maxn],w[maxn][maxn];
int dp[maxn];
int main()
{
    int N,V;
    cin>>N>>V;
    for (int i = 0; i < N; i ++ ){
        cin>>s[i];
        for(int j=0;j<s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    for(int i=0;i<N;i++){
        for(int j=V;j>=0;j--){
            for(int k=0;k<s[i];k++){
                if(j>=v[i][k]) dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout<<dp[V]<<endl;
}

有依赖背包

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int f[110][110];
vector<int> g[110];
int v[110],w[110];
int n,m,root;

int dfs(int x){
    for(int i=v[x];i<=m;i++) f[x][i]=w[x];//x一定要选,选上
    for(int i=0;i<g[x].size();i++){//遍历子树
        int y=g[x][i];
        dfs(y);
        for(int j=m;j>=v[x];j--){//只选一次
            for(int k=0;k<=j-v[x];k++){//组内挑选
                f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int fa;
        cin>>v[i]>>w[i]>>fa;
        if(fa==-1) root=i;
        else g[fa].push_back(i);
    }
    dfs(root);
    cout<<f[root][m];
}

背包问题求方案数

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1001;
const int mod=1e9+7;
int dp[N],cnt[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<=m;i++) cnt[i]=1;//啥也没有也是一种方案
    for(int i=0;i<n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--){
            int s=dp[j-v]+w;
            if(s>dp[j]){//小了就更新
                dp[j]=s;
                cnt[j]=cnt[j-v];
            }else if(s==dp[j]){
                cnt[j]=(cnt[j]+cnt[j-v])%mod;//相等就累加
            }
        }
    }
    cout<<cnt[m]<<endl;
}

背包问题求方案

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;//数据范围稍微开大点不然报错
int dp[N][N],v[N],w[N];
int main()
{
    int n,m;
    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++){
            dp[i][j]=dp[i+1][j];
            if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i+1][j-v[i]]+w[i]);
        }
    }
    int cur_v=m;
    for(int i=1;i<=n;i++){
        if(cur_v<=0) break;
        if(cur_v>=v[i] && dp[i][cur_v]==dp[i+1][cur_v-v[i]]+w[i]){
            cout<<i<<' ';
            cur_v-=v[i];
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值