几种背包问题(简单的动态规划)

背包问题,基本上都是以一个固定的容积S,和n件有重量cost和价值value的物品,求价值最大的一种放法。
有人说背包问题实际上运用了贪心的思想,但是我觉得背包的思路与贪心大不相同,贪心的算法更多的是针对每一步变化的策略选取最优解,而不是想背包问题一样对于每一个可放置重量都进行规划,背包是一个动态的过程。
  1. 首先我们来说说最简单的01背包问题,每个物品只能放一次,我们采取的做法是将所有可以放下的物品都进行判断,是否最优,如果最优则更新最优解

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e3 + 7;
    typedef long long ll;
    int cost[N],value[N],dp[N][N]; // 存储花费,价值,动态规划数组
    int main ()
    {
        int n,S; // n表示物品个数,S表示背包容量
        cin >> n >> S;
        memset(dp,0,sizeof dp); // dp数组清零
        for(int i = 1;i <= n;++i)
            cin >> cost[i] >> value[i];
        for(int i = 1;i <= n;++i)
            for(int j = 0;j <= S;++j){
                dp[i][j] = i == 1 ? 0 : dp[i - 1][j]; // 继承上一件物品规划时候的状态
                if(j >= cost[i]) dp[i][j] = max(dp[i - 1][j - cost[i]] + value[i],dp[i][j]); // 进行最优的动态规划
            }
        cout << dp[n][S] << endl;
        return 0;
    }
    
    很显然,对该种规划可以在输入的时候进行,以减小对空间的消耗。
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e3 + 7;
    typedef long long ll;
    int dp[N][N];
    int main ()
    {
        int n,S; 
        cin >> n >> S;
        memset(dp,0,sizeof dp);
        int cost,value;
        for(int i = 1;i <= n;++i){
            cin >> cost >> value;
            for(int j = 0;j <= S;++j){
                dp[i][j] = i == 1 ? 0 : dp[i - 1][j];
                if(j >= cost) dp[i][j] = max(dp[i - 1][j - cost] + value,dp[i][j]);
            }
        }
        cout << dp[n][S] << endl;
        return 0;
    }
    
    当我们将这种变化用表格的形式打出来的时候,我们惊讶的发现,对于物品个数的规划是没有必要的,我们只需要针对每一种花销进行规划,这样我们只需要用一维数组就可以完成上述过程,大大的减小了对空间的消耗。
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e3 + 7;
    typedef long long ll;
    int dp[N];
    int main ()
    {
        int n,S; //
        cin >> n >> S;
        memset(dp,0,sizeof dp);
        int cost,value;
        for(int i = 1;i <= n;++i){
            cin >> cost >> value;
            for(int j = S;j >= 0;--j){
                if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]); // 针对每个可放容量来选择放与不放
            }
        }
        cout << dp[S] << endl;
        return 0;
    }
  2. 完全背包问题 ,完全背包问题就是在01背包问题的基础上将每个物品可放次数变成了无限,我们只需要将01背包的代码改成顺着遍历即可。因为顺着遍历,该种物品就在每个容量上被多次规划了。
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e3 + 7;
    typedef long long ll;
    int dp[N];
    int main ()
    {
        int n,S; //
        cin >> n >> S;
        memset(dp,0,sizeof dp);
        int cost,value;
        for(int i = 1;i <= n;++i){
            cin >> cost >> value;
            for(int j = cost;j <= S;++j){
                if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]);
            }
        }
        cout << dp[S] << endl;
        return 0;
    }

  3. 多重背包问题.

题目限制了每种物品的购买次数.所以我们可以将多出的部分按照01背包的规划方式进行规划.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
int dp[N];
int main ()
{
    int n,S; //
    cin >> n >> S;
    memset(dp,0,sizeof dp);
    int cost,value,time;
    for(int i = 1;i <= n;++i){
        cin >> cost >> value >> time;
        while(time--){
            for(int j = S;j >= cost;--j)
                if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]);
        }
    }
    cout << dp[S] << endl;
    return 0;
}

由于wustoj只能判多组数据...所以ac代码就是加上while循环...

4.混合背包问题
其实后面的背包问题基本就是由01背包和完全背包的组合.

而混合背包其实就是01背包,完全背包,多重背包的大杂烩.还是上例题吧.
在多重背包的基础上多了一个如果个数上限为0即可购买个数无上限.

其实就是在多重背包的基础上多了一次条件判断,决定是顺着遍历还是逆着遍历.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
int dp[N];
int main ()
{
    int n,S;
    cin >> n >> S;
    memset(dp,0,sizeof dp);
    int cost,value,time;
    for(int i = 1;i <= n;++i){
        cin >> cost >> value >> time;
        if(time)
            for(int o = 0;o < time;++o)
                for(int j = S;j >= cost;--j)
                    dp[j] = max(dp[j - cost] + value,dp[j]);
        else
            for(int j = cost;j <= S;++j)
                dp[j] = max(dp[j - cost] + value,dp[j]);
    }
    cout << dp[S] << endl;
    return 0;
}

5.多种开销的背包问题(这里只说两种开销的背包问题,更多开销的背包问题方法一样)
其实这种问题与01背包一样,只不过从01背包的一个开销规划变成了两种,通过一个二维数组就可以实现.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
int dp[N][N];
int main ()
{
    int n,S1,S2;
    cin >> S1 >> S2 >> n;
    memset(dp,0,sizeof dp);
    int cost1,cost2,value;
    for(int i = 1;i <= n;++i){
        cin >> cost1 >> cost2 >> value;
        for(int j = S1,k = S2;j >= cost1 && k >= cost2;--j,--k)
            dp[j][k] = max(dp[j - cost1][k - cost2] + value,dp[j][k]);
    }
    cout << dp[S1][S2] << endl;
    return 0;
}

上一个例题,wustoj 1873潜水员,这个题目也是一个二维背包问题,但是题目要求两种开销达到某一值后的最小值,这就比较有意思了,我们采取的策略是设置两个计数器来记忆已花费开销,当两种开销都达到标准之后按照标准值进行规划。跟01背包相比不需要小于背包的容量。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int dp[N][N];
int main ()
{
    int n,S1,S2;
    cin >> S1 >> S2 >> n;
    memset(dp,inf,sizeof dp);
    dp[0][0] = 0;
    int cost1,cost2,value,date1 = 0,date2 = 0;
    for(int i = 1;i <= n;++i){
        cin >> cost1 >> cost2 >> value;
        for(int j = S1;j >= 0;--j)
            for(int k = S2;k >= 0;--k){
                date1 = j + cost1;
                date2 = k + cost2;
                if(date1 > S1) date1 = S1;
                if(date2 > S2) date2 = S2;
                dp[date1][date2] = min(dp[j][k] + value,dp[date1][date2]);
            }
    }
    cout << dp[S1][S2] << endl;
    return 0;
}

6.分组背包

分组背包是在01背包的基础上将物品分组,同一组的物品只能同时存在一个。看起来好像不好下手,其实想通了也很简单。

我们将每一组物品都用同一层状态转移这样,我们可喜的发现,我们的问题就直接转化成了01背包问题。
还是上一个例题吧

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int dp[N];
int cost[N][N],value[N][N];
int main ()
{
    int n,S,t;
    cin >> S >> n >> t;
    memset(dp,0,sizeof dp);
    memset(cost,0,sizeof cost);
    memset(value,0,sizeof value);
    int num,c,v;
    for(int i = 1;i <= n;++i){
        cin >> c >> v >> num;
        cost[num][++cost[num][0]] = c;
        value[num][++value[num][0]] = v;
    }
    for(int i = 1;i <= t;++i)
        for(int j = S;j >= 0;--j)
            for(int k = 1;k <= cost[i][0];++k)
                if(j >= cost[i][k]) dp[j] = max(dp[j - cost[i][k]] + value[i][k],dp[j]);
    cout << dp[S] << endl;
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_41791981/article/details/80518478