一、多重背包

1、介绍

物品个数是多个,但不是无穷个。

例题:多重背包

登录—专业IT笔试面试备考平台_牛客网

2、分析

dp[i][j]表示在[1, i]中选,重量不超过j的最大价值
不选i:dp[i][j] = dp[i - 1][j];
选i:由于物品个数不是无限的,所以不能优化只能一个一个写
选一个:dp[i][j] = dp[i - 1][j - w[i]] + v[i];
选二个:dp[i][j] = dp[i - 1][j - 2 * w[i]] + 2 * v[i];
..... 
选最多x[i]个:dp[i][j] = dp[i - 1][j - x[i] * w[i]] + x[i] * v[i];
当然枚举过程中j - k * w[i] >= 0
初始化返回值都和之前问题一样

3、代码

#include <bits/stdc++.h>
using namespace std;
int w[110];
int v[110];
int x[110];
int n, t;

int dp[110][110];
int main()
{
    cin >> n >> t;
    for(int i = 1; i <= n; i++)
        cin >> x[i] >> w[i] >> v[i]; 
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= t; j++)
        {
            for(int k = 0; k <= x[i] && j - k * w[i] >= 0; k++)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);
        }
    }
    cout << dp[n][t];
    return 0;
}

4、空间优化

只用到了左上方的数据,所以从右向左

#include <bits/stdc++.h>
using namespace std;
int w[110];
int v[110];
int x[110];
int n, t;

int dp[110];
int main()
{
    cin >> n >> t;
    for(int i = 1; i <= n; i++)
        cin >> x[i] >> w[i] >> v[i]; 
    for(int i = 1; i <= n; i++)
    {
        for(int j = t; j >= 0; j--)
        {
            for(int k = 0; k <= x[i] && j - k * w[i] >= 0; k++)
                dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
        }
    }
    cout << dp[t];
    return 0;
}

5、二进制优化

二进制优化:优化后最终结果会把多重背包变成01背包
其实多重背包不做任何优化也能看作01背包,因为个数有限,可以把每一个物品拆出来
基于这种思想不难想到可以把同一种物品的10进制个数通过二进制表示出来,这样优化的是logX
假设一个物品个数是9,重量w,价值v
9 = 1 + 2 + 4 + 2,即可以看成有4种物品分别的重量和价值是[w, v], [2w, 2v], [4w, 4v], [2w, 2v]
然后进行01背包处理
这样从O(n * t * x)->O(n * t * logx)
没有数量数组,原数组由于优化,每种物品分成了logX种物品,所以大小还要乘logX

求方案数不能二进制优化,因为同一种物品被我们分成了logx种物品
即优化后的不同数据不能代表不同的物品,也就不能代表一种方案

int w[5 * 110];
int v[5 * 110];
int n, t;
int sz; // 新的个数用sz记录
int dp[110];
int main()
{
    cin >> n >> t;
    for(int i = 1; i <= n; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        // 同一物品开始二进制分类
        int p = 1;
        while(x >= p)
        {
            sz++;
            x -= p;
            w[sz] = y * p;
            v[sz] = z * p;
            p *= 2;
        }
        if(x)
        {
            sz++;
            w[sz] = y * x;
            v[sz] = z * x;
        }
    }
    
    for(int i = 1; i <= sz; i++)
    {
        for(int j = t; j >= w[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    cout << dp[t];
    
    return 0;
}

二、分组背包

1、介绍

在每一组中挑一个物品

例题:分组背包

P1757 通天之分组背包 - 洛谷

2、分析

dp[i][j]表示在小组[1, i]中选,每组选一个物品,重量不超过j的最大价值
不选i: dp[i][j] = dp[i - 1][j];
选i: 选i组的第一个: dp[i][j] = dp[i - 1][j - w[i][0]] + v[i][0];
选i: 选i组的第二个: dp[i][j] = dp[i - 1][j - w[i][1]] + v[i][1];
.....
选i: 选i组的第x个: dp[i][j] = dp[i - 1][j - w[i][x - 1]] + v[i][x - 1];

3、代码

// https://www.luogu.com.cn/problem/P1757
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
int n, m;
vector<pii> g[110];
int k = 0;

int dp[110][1010];
int main()
{
    cin >> m >> n;
    for(int i = 1; i <= n; i++)
    {
        int W, V, K;
        cin >> W >> V >> K;
        g[K].push_back({W, V});
        k = max(k, K);
    }
    for(int i = 1; i <= k; i++)
    {
        for(int j = m; j >= 0; j--)
        {
            dp[i][j] = dp[i - 1][j];
            for(auto& [a, b] : g[i])
            {
                if(j - a >= 0)
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - a] + b);
            }
        }
    }
    cout << dp[k][m];
    return 0;
}

4、空间优化

空间优化:用的是左上,所以从右向左

int dp[1010];
int main()
{
    cin >> m >> n;
    for(int i = 1; i <= n; i++)
    {
        int W, V, K;
        cin >> W >> V >> K;
        g[K].push_back({W, V});
        k = max(k, K);
    }
    for(int i = 1; i <= k; i++)
    {
        for(int j = m; j >= 0; j--)
        {
            for(auto& [a, b] : g[i])
            {
                if(j - a >= 0)
                    dp[j] = max(dp[j], dp[j - a] + b);
            }
        }
    }
    cout << dp[m];
    return 0;
}

三、混合背包

1、介绍

01背包,完全背包,多重背包,分组背包结合在一起的背包问题

例题:樱花

P1833 樱花 - 洛谷

2、分析

就是遇到一种物品属于什么背包问题就用对应的状态方程解决即可。

3、代码

int m, n;
int t[10010];
int c[10010];
int p[10010];

int dp[10010][1010];
int main()
{
    int h1, m1, h2, m2;
    char ch;
    cin >> h1 >> ch >> m1 >> h2 >> ch >> m2 >> n;
    m = h2 * 60 + m2 - h1 * 60 - m1;
    for(int i = 1; i <= n; i++)
        cin >> t[i] >> c[i] >> p[i];
    for(int i = 1; i <= n; i++)
    {
        if(p[i] == 0)
        {
            for(int j = 0; j <= m; j++)
            {
                dp[i][j] = dp[i - 1][j];
                if(j - t[i] >= 0)
                    dp[i][j] = max(dp[i][j], dp[i][j - t[i]] + c[i]);
            }
        }
        else 
        {
            for(int j = m; j >= 0; j--)
            {
                dp[i][j] = dp[i - 1][j];
                for(int k = 1; k <= p[i] && j - k * t[i] >= 0; k++)
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - k * t[i]] + k * c[i]);
            }
        }
    }
    cout << dp[n][m];
    return 0;
}

4、空间优化

注意完全背包是从左向右即可

int dp[1010];
int main()
{
    int h1, m1, h2, m2;
    char ch;
    cin >> h1 >> ch >> m1 >> h2 >> ch >> m2 >> n;
    m = h2 * 60 + m2 - h1 * 60 - m1;
    for(int i = 1; i <= n; i++)
        cin >> t[i] >> c[i] >> p[i];
    for(int i = 1; i <= n; i++)
    {
        if(p[i] == 0)
        {
            for(int j = t[i]; j <= m; j++)
            {
               dp[j] = max(dp[j], dp[j - t[i]] + c[i]);
            }
        }
        else 
        {
            for(int j = m; j >= t[i]; j--)
            {
                for(int k = 1; k <= p[i] && j - k * t[i] >= 0; k++)
                    dp[j] = max(dp[j], dp[j - k * t[i]] + k * c[i]);
            }
        }
    }
    cout << dp[m];
    return 0;
}

四、多维背包

1、介绍

限制条件增多的背包问题,必须要空间优化。

例题:L 国的战斗之间谍

P1910 L 国的战斗之间谍 - 洛谷

2、分析

dp[i][j][k]表示在[1, i]中选,暴露不超过j,工资不超过j的最大价值
不选i: dp[i][j][j] = dp[i - 1][j][k];
  选i: dp[i][j][k] = dp[i - 1][j - b[i]][k - c[i]] + a[i];

3、空间优化代码

从右向左

// https://www.luogu.com.cn/problem/P1910
// 多维背包:限制条件变多,那就加维数
#include <bits/stdc++.h>
using namespace std;
int n, m, x;
int a[110];
int b[110];
int c[110];
int dp[1010][1010];

// dp[i][j][k]表示在[1, i]中选,暴露不超过j,工资不超过j的最大价值
// 不选i: dp[i][j][j] = dp[i - 1][j][k];
//   选i: dp[i][j][k] = dp[i - 1][j - b[i]][k - c[i]] + a[i];
// 必须空间优化
int main()
{
    cin >> n >> m >> x;
    for(int i = 1; i <= n; i++)
        cin >> a[i] >> b[i] >> c[i];
    for(int i = 1; i <= n; i++)
    {
        for(int j = m; j >= b[i]; j--)
        {
            for(int k = x; k >= c[i]; k--)
            {
                dp[j][k] = max(dp[j][k], dp[j - b[i]][k - c[i]] + a[i]);
            }
        }
    }
    cout << dp[m][x];
    return 0;
}

五、例题

1、摆花

P1077 [NOIP 2012 普及组] 摆花 - 洛谷

#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[110];
const int mod = 1e6 + 7;

// dp[i][j]表示在[1, i]种物品种选,个数刚好j的总方案数
// 不选i:   dp[i][j] = dp[i - 1][j];
// 选一个i: dp[i][j] += dp[i - 1][j - 1];
// 选二个i: dp[i][j] += dp[i - 1][j - 2];
// ....
// 选a[i]个i: dp[i][j] += dp[i - 1][j - a[i]];
// j - x >= 0;
// 初始化:dp[0][0] = 1; dp[0][j] = 0

int main()
{
    cin >> n >> m;
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
    dp[0][0] = 1;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= m; j++)
        {
            for(int k = 0; k <= a[i] && j - k >= 0; k++)
                dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
        }
    }
    cout << dp[n][m];
    return 0;
}

2、排兵布阵

P5322 [BJOI2019] 排兵布阵 - 洛谷

#include <bits/stdc++.h>
using namespace std;
int s, n, m;
int g[110][110];
int dp[110][20010];

// https://www.luogu.com.cn/problem/P5322
// 对于每一个人的每一个城堡我们都能*2+1找到最少占领这个城堡的分配士兵数
// 即问题转化成了在每一组城堡中我挑出一个数,计算能占领几个这样的城堡
// dp[i][j]表示在[1, i]的城堡中,派出不超过m名士兵后的最大得分
// 存储数组g[110][110],一维城堡编号,二维玩家编号,每一行排好序
// 不派:dp[i][j] = dp[i - 1][j];
// 派g[i][p]的士兵争夺城堡i: dp[i][j] = dp[i - 1][j - g[i][p]] + i * p;
int main()
{
    cin >> s >> n >> m;
    for(int i = 1; i <= s; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            cin >> g[j][i];
            g[j][i] = 2 * g[j][i] + 1;
        }
    }
    for(int i = 1; i <= n; i++)
        sort(g[i] + 1, g[i] + s + 1);
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= m; j++)
        {
            dp[i][j] = dp[i - 1][j];
            for(int p = 1; p <= s; p++)
            {
                if(j - g[i][p] >= 0)
                    dp[i][j] = max(dp[i][j], dp[i - 1][j - g[i][p]] + p * i);
            }
        }
    }
    cout << dp[n][m];
    return 0;
}
Logo

集算法之大成!助力oier实现梦想!

更多推荐