前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【动态规划/背包问题】特殊的多维费用背包问题

【动态规划/背包问题】特殊的多维费用背包问题

作者头像
宫水三叶的刷题日记
发布2021-08-13 10:50:29
发布2021-08-13 10:50:29
1.3K00
代码可运行
举报
运行总次数:0
代码可运行

前言

今天是我们讲解「动态规划专题」中的「背包问题」的第十五篇

今天将完成一道“特殊”的「多维背包」问题。

另外,我在文章结尾处列举了我所整理的关于背包问题的相关题目。

背包问题我会按照编排好的顺序进行讲解(每隔几天更新一篇,确保大家消化)。

你可以先尝试做做,也欢迎你向我留言补充,你觉得与背包相关的 DP 类型题目 ~

题目描述

这是 LeetCode 上的「879. 盈利计划」,难度为「困难」

Tag : 「动态规划」、「容斥原理」、「数学」、「背包问题」、「多维背包」

集团里有

n

名员工,他们可以完成各种各样的工作创造利润。

i

种工作会产生

profit[i]

的利润,它要求

group[i]

名成员共同参与。

如果成员参与了其中一项工作,就不能参与另一项工作。

工作的任何至少产生

minProfit

利润的子集称为「盈利计划」。并且工作的成员总数最多为

n

有多少种计划可以选择?

因为答案很大,所以 返回结果模

10^9 + 7

的值。

示例 1:

代码语言:javascript
代码运行次数:0
运行
复制
输入:n = 5, minProfit = 3, group = [2,2], profit = [2,3]

输出:2

解释:至少产生 3 的利润,该集团可以完成工作 0 和工作 1 ,或仅完成工作 1 。
总的来说,有两种计划。

示例 2:

代码语言:javascript
代码运行次数:0
运行
复制
输入:n = 10, minProfit = 5, group = [2,3,5], profit = [6,7,8]

输出:7

解释:至少产生 5 的利润,只要完成其中一种工作就行,所以该集团可以完成任何工作。
有 7 种可能的计划:(0),(1),(2),(0,1),(0,2),(1,2),以及 (0,1,2) 。

提示:

  • 1 <= n <= 100
  • 0 <= minProfit <= 100
  • 1 <= group.length <= 100
  • 1 <= group[i] <= 100
  • profit.length == group.length
  • 0 <= profit[i] <= 100

动态规划

这是一类特殊的多维费用背包问题。

将每个任务看作一个「物品」,完成任务所需要的人数看作「成本」,完成任务得到的利润看作「价值」。

其特殊在于存在一维容量维度需要满足「不低于」,而不是常规的「不超过」。这需要我们对于某些状态作等价变换。

定义

f[i][j][k]

为考虑前

i

件物品,使用人数不超过

j

,所得利润至少为

k

的方案数。

对于每件物品(令下标从

1

开始),我们有「选」和「不选」两种决策:

  • 不选:显然有:
f[i - 1][j][k]
  • 选:首先需要满足人数达到要求(
j >= group[i - 1]

),还需要考虑「至少利润」负值问题:如果直接令「利润维度」为

k - profit[i - 1]

可能会出现负值,那么负值是否为合法状态呢?这需要结合「状态定义」来看,由于是「利润至少为

k

」,因此属于「合法状态」,需要参与转移。 由于我们没有设计动规数组存储「利润至少为负权」状态,我们需要根据「状态定义」做一个等价替换,将这个「状态」映射到

f[i][j][0]

。这主要是利用所有的任务利润都为“非负数”,所以不可能出现利润为负的情况,这时候「利润至少为某个负数

k

」的方案数其实是完全等价于「利润至少为

0

」的方案数。

f[i - 1][j - group[i - 1]][\max(k - profit[i - 1], 0)]

最终

f[i][j][k]

为上述两种情况之和。

然后考虑「如何构造有效起始值」问题,还是结合我们的「状态定义」来考虑:

当不存在任何物品(任务)时,所得利用利润必然为

0

(满足至少为

0

),同时对人数限制没有要求。

因此可以让所有

f[0][x][0] = 1

代码:

代码语言:javascript
代码运行次数:0
运行
复制
class Solution {
    int mod = (int)1e9+7;
    public int profitableSchemes(int n, int min, int[] gs, int[] ps) {
        int m = gs.length;
        // f[i][j][k] 代表考虑前 i 个任务,使用人数不超过 j,产生利益至少 k 的方案数 
        int[][][] f = new int[m+1][n+1][min+1];
        // 初始化:当没有任务时,无论有多少人,只有利益至少为 0 时的方案数为 1,其他为 0 
        for (int j = 0; j <= n; j++) {
            f[0][j][0] = 1;
        }
        for (int i = 1; i <= m; i++) {
            int g = gs[i-1], p = ps[i-1];
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k <= min; k++) {
                    f[i][j][k] = f[i-1][j][k];
                    if (j >= g) {
                        f[i][j][k] += f[i-1][j-g][Math.max(0, k-p)];
                        f[i][j][k] %= mod;
                    }
                }
            }
        }
        return f[m][n][min];
    }
}
  • 时间复杂度:
O(m * n * min)
  • 空间复杂度:
O(m * n * min)

动态规划(作差法)

基本思路是先不考虑最小利润 minProfit,求得所有只受「人数限制」的方案数 a

然后求得考虑「人数限制」同时,利润低于 minProfit(不超过 minProfit - 1)的所有方案数 b

最后由 a - b 即是答案。

❝为了不额外增加难度,这里直接使用 Java 的高精度实现,Python 的同学可以直接不考虑精度问题,C++ 同学则需要自己实现高精度。 ❞

代码:

代码语言:javascript
代码运行次数:0
运行
复制
import java.math.BigInteger;
class Solution {
    static int N = 105;
    static BigInteger[][] f = new BigInteger[2][N]; 
    static BigInteger[][][] g = new BigInteger[2][N][N];
    static BigInteger mod = new BigInteger("1000000007");
    
    public int profitableSchemes(int n, int min, int[] gs, int[] ps) {
        int m = gs.length;

        for (int j = 0; j <= n; j++) {
            f[0][j] = new BigInteger("1"); 
            f[1][j] = new BigInteger("0"); 
        }
        for (int j = 0; j <= n; j++) {
            for (int k = 0; k <= min; k++) {
                g[0][j][k] = new BigInteger("1"); 
                g[1][j][k] = new BigInteger("0"); 
            }
        }

        for (int i = 1; i <= m; i++) {
            int a = gs[i - 1], b = ps[i - 1];
            int x = i & 1, y = (i - 1) & 1;
            for (int j = 0; j <= n; j++) {
                f[x][j] = f[y][j];
                if (j >= a) {
                    f[x][j] = f[x][j].add(f[y][j-a]);
                } 
            }
        }
        if (min == 0) return (f[m&1][n]).mod(mod).intValue();

        for (int i = 1; i <= m; i++) {
            int a = gs[i - 1], b = ps[i - 1];
            int x = i & 1, y = (i - 1) & 1;
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k < min; k++) {
                    g[x][j][k] = g[y][j][k];
                    if (j - a >= 0 && k - b >= 0) {
                        g[x][j][k] = g[x][j][k].add(g[y][j-a][k-b]);
                    } 
                }
            }
        }

        return f[m&1][n].subtract(g[m&1][n][min-1]).mod(mod).intValue();
    }
}
  • 时间复杂度:第一遍 DP 复杂度为
O(m * n)

;第二遍 DP 复杂度为

O(m * n * min)

。整体复杂度为

O(m * n * min)
  • 空间复杂度:
O(m * n * min)

总结

今天我们完成了一道“特殊”的「多维费用背包问题求方案数」的题目。

与传统的背包问题不同,本题有一维费用是「至少」,而不是一般性的「不超过」或「恰好」。

这时候我们需要结合状态定义的实际意义来做「等价替换」(解法一),或者利用「容斥原理」来将问题转化为“传统”的背包问题进行求解(解法二)。

一般来说,方式一更具有一般性,方式二会随着带「至少」限制的维度的增加,带来代码量的增多和复杂度的上升。

背包问题(目录)

  1. 01背包 : 背包问题 第一讲
    1. 【练习】01背包 : 背包问题 第二讲
    2. 【学习&练习】01背包 : 背包问题 第三讲
  2. 完全背包 : 背包问题 第四讲
    1. 【练习】完全背包 : 背包问题 第五讲
    2. 【练习】完全背包 : 背包问题 第六讲
    3. 【练习】完全背包 : 背包问题 第七讲
  3. 多重背包 : 背包问题 第八讲
  4. 多重背包(优化篇)
    1. 【上】多重背包(优化篇): 背包问题 第九讲
    2. 【下】多重背包(优化篇): 背包问题 第十讲
  5. 混合背包 : 背包问题 第十一讲
  6. 分组背包 : 背包问题 第十二讲
    1. 【练习】分组背包 : 背包问题 第十三讲
  7. 多维背包
    1. 【练习】多维背包 : 背包问题 第十四讲
    2. 【练习】多维背包 : 本篇
  8. 树形背包
    1. 【练习篇】树形背包
  9. 背包求方案数
    1. 【练习】背包求方案数
  10. 背包求具体方案
    1. 【练习】背包求具体方案
  11. 泛化背包
    1. 【练习】泛化背包

最后

这是我们「刷穿 LeetCode」系列文章的第 No.879 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 宫水三叶的刷题日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 题目描述
  • 动态规划
  • 动态规划(作差法)
  • 总结
  • 背包问题(目录)
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档