背包问题总结

本文深入探讨了动态规划在解决背包问题中的应用,包括01背包、完全背包、多重背包和二维费用背包。通过实例解析了各类型背包问题的递推公式和优化策略,如二维转一维、二进制优化等,并提供了相应的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勇敢nn

心动不如行动,感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值