[算法基础]动态规划——背包问题

01背包问题

N个物品,容量为V的背包
物品有两个数学——Vi体积,Wi价值
每件物品最多使用一次——要么使用0次、要么使用1次,最多使用一次
使得背包能装得下的情况下,最大价值为多少?
在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N]; // 体积和价值的数组
int f[N][N];
int main()
{
    int n, m;
    cin >> n >> m; // 物品数量和背包容量
    for (int i = 1; i <= n; ++i) // 1~n 总共n个物品
    {
        cin >> v[i] >> w[i];
    }
    // 一开始由于f是全局,因此f[0][1~m] 前0个物品使得总体积不超过1~m的状态方程为0
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 0; j <= m; ++j)
        {
            // 划分两个集合,不包含第i个物品
            f[i][j] = f[i - 1][j];
            if (j >= v[i])
            {
                // 含第i个物品,在包含i和不包含i之间选择一个最大值
                f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

优化
转化为一维:
若j从小到大,f[j-v[i]]中,由于j-v[i]小于j,f[j-v[i]]已经在i这层循环被计算了,而我们想要的f[j-v[i]]应该是i-1层循环里面的,所以j从大到小的话保证此时的f[j-v[i]]还未被计算,也就是第i-1层的数据

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N]; // 体积和价值的数组
int f[N];
int main()
{
    int n, m;
    cin >> n >> m; // 物品数量和背包容量
    for (int i = 1; i <= n; ++i) // 1~n 总共n个物品
    {
        cin >> v[i] >> w[i];
    }
    // 一开始由于f是全局,因此f[0][1~m] 前0个物品使得总体积不超过1~m的状态方程为0
    for (int i = 1; i <= n; ++i)
    {
        for (int j = m; j >= v[i]; --j) // if条件原本要满足j >= v[i],因此j范围在0~v[i - 1]是没有用的,可以直接不考虑
        {
            // 由于f[i]只用到了上一层f[i - 1],因此可以直接变为1维
            /*
            f[i][j] = f[i - 1][j]; -> f[j] = f[j]; 
            */
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m] << endl;
    return 0;
}

完全背包

每件物品有无限个,只要体积够用就可以无限装

O(n^3)初始版本

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int m, n;
int main()
{
    cin >> m >> n;
    for (int i = 1; i <= m; ++i)
    {
        cin >> v[i] >> w[i];
    }
    for (int i = 1; i <= m; ++i) // 第i个物品
    {
        for (int j = 0; j <= n; ++j) // j空间
        {
            for (int k = 0; k*v[i] <= j; ++k) // 选k个第i个物品 
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]); 
                // 优化前:① max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]); 
                // 而不是:② max(f[i - 1][j], f[i - 1][j - k*v[i]] + k*w[i]); 
                // k = 0时的情况,其实就是 ① 的情况 ,因为初识数组是全局默认全为0
                // 因此一开始一定是先f[i][j] = max(f[i][j], f[i - 1][j]) -> k = 0
                // 因此在之后的过程中一定会将f[i][j]进行更新 如果写②的情况下,不一定会保证每次都把它往大了更新
            }
        }
    }
    cout << f[m][n] << endl;
    return 0;
}


如何将他优化为二维的?
f[i][j] = f[i-1][j-v[i]*k] + w[i]*k展开:
①f[i][j] = max(f[i-1,j], f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w,…)
②f[i,j-v] = max( f[i-1][j-v], f[i-1,j-2v]+w, …)
对于下面这个式子解释:包含一个物品,f[i-1,j-2v]
这样子就会发现,f[i][j]的最大值比f[i][j-v]大w
就可以优化为:f[i,j] = max(f[i-1][j], f[i][j-v]+w)
因此从枚举k个优化到枚举2个了
对比01背包,01背包是从i-1转移过来,完全背包是从i转移过来

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int m, n;
int main()
{
    cin >> m >> n;
    for (int i = 1; i <= m; ++i)
    {
        cin >> v[i] >> w[i];
    }
    for (int i = 1; i <= m; ++i) // 第i个物品
    {
        for (int j = 0; j <= n; ++j) // j空间
        {
            f[i][j] = f[i - 1][j];
            if (j >= v[i])
            {
                f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
            }
        }
    }
    cout << f[m][n] << endl;
    return 0;
}


优化到一维:——参考01背包

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];
int m, n;
int main()
{
    cin >> m >> n;
    for (int i = 1; i <= m; ++i)
    {
        cin >> v[i] >> w[i];
    }
    for (int i = 1; i <= m; ++i) // 第i个物品
    {
        for (int j = v[i]; j <= n; ++j) // j空间
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[n] << endl;
    return 0;
}

多重背包

每个物品最多有Si个,不同物品Si可能不一样
思路与完全背包一样

#include <iostream>
using namespace std;
const int N = 110;
int v[N], w[N], s[N];
int f[N][N];
int main()
{
    int m, n;
    cin >> m >> n;
    for (int i = 1; i <= m; ++i)
    {
        cin >> v[i] >> w[i] >> s[i];
    }
    for (int i = 1; i <= m; ++i)
    {
        for (int j = 0; j <= n; ++j)
        {
            for (int k = 0; k <= s[i] && k*v[i] <= j; ++k)
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k*v[i]] + k*w[i]);
            }
        }
    }
    cout << f[m][n] << endl;
    return 0;
}

使用二进制方法进行优化

#include <iostream>
using namespace std;
const int N = 20100;
int f[N];
int v[N], w[N];
int main()
{
    int n, m;
    cin >> n >> m;
    int cnt = 0; 
    for (int i = 1; i <= n; ++i)
    {
        int a, b, s; // 分别输入数量、体积、最大个数
        cin >> a >> b >> s;
        int k = 1; // 二进制
        while (k <= s) // 只要该分法还没有达到最大值,就一直分下去,分到最后要么与s相等,要么还相差常数c个
        {
            cnt++; // 最后新的物品容量——相当于有cnt个篮子
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0) // 如果还有剩余
        {
            cnt++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    n = cnt;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = m; j >= v[i]; --j) // 01背包问题
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m] << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值