首页 > 其他分享 >1723. 完成所有工作的最短时间

1723. 完成所有工作的最短时间

时间:2022-10-27 11:57:40浏览次数:48  
标签:状态 需要 短时间 sub mask 1723 枚举 子集 完成

题目描述

给了一个整数数组jobs表示工作,元素ei表示第i个工作需要花费的时间
给k个人,每个工作都需要分配,且每个只能给一个人
问如果要完成所有工作,求最短的工作时间?

f1-状态压缩+动态规划

基本分析

  1. 能看出来需要二进制压缩状态,这里n是工作量,k是人数,怎么定义dp定义好?因为给第i个人的分配工作的时候只和之前i-1个人被分配的工作有关,因此定义dp状态为f[i][j],表示给前i个人分配工作状态为j时候的最小时间
  2. 考虑怎么转移?加入给第i个人分配的工作是mask的一个子集sub,那么前i-1个人做的工作就是mask^sub,本次分配对应的时间就是max(f[i-1][mask^sub],value[i]);因为这个不一定是最合理的,我们需要枚举mask的子集sub,来让总和最小
  3. 考虑以上转移,是不是可以对value做预处理?可以遍历mask的子集sub,把sub对应的时间存起来
  4. 怎么考虑dp的初始状态?考虑只由第一个人做的时候,mask对应的时间就是我们上一步求的sub对应的时间。
  5. 转移的时候怎么考虑?
    • (1)先遍历人i,因为i=0给了初值,且要看i-1的状态,i从1开始就行
    • (2)再遍历状态sub,对应每个sub给一大的初始时间inf,再初始化他的子集last=sub
    • (3)枚举sub的子集last,看不同的prev和last的组合时间是多少,选出最小的就是f[i][sub]的结果
  6. 以上复杂度很高,可以考虑怎么优化?
    • (1)python的min和max比较复杂,需要写成直接比较值的形式
    • (2)在函数之外,把mask的子集存起来,写成subsets[mask]的形式
    • (3)在枚举子集的时候借用二分等,后面再细看

代码

subsets = [[] for _ in range(1<<12)]
for i in range(1<<12):
    s = i
    while s > 0:
        subsets[i].append(s)
        s = (s-1) & i

class Solution:
    def minimumTimeRequired(self, jobs: List[int], k: int) -> int:
        n = len(jobs)

        mask = 1<<n
        ssum = [0] * mask
        for sub in range(mask):
            for j in range(n):
                if sub & (1<<j):
                    ssum[sub] = ssum[sub ^ (1<<j)] + jobs[j]
                    break
        f = [[0]*mask for _ in range(k)]
        f[0] = ssum.copy()

        for i in range(1, k):
            for j in range(1, mask):
                last = j
                mint = inf
                for last in subsets[j]:
                    prev = j - last
                    curt = ssum[last] if ssum[last] > f[i-1][prev] else f[i-1][prev]
                    mint = mint if mint <= curt else curt
                f[i][j] = mint
                
        return f[-1][-1]

复杂度

时间:\(需要O(2^n)来预处理数组,dp有O(n \cdot 2^n种状态), 将状态看做集合,大小为k的集合有n \ cdot C_{n}^{k}2^k=3^n个, 总时间复杂度是O(n \cdot 3^n)\)
空间:\(O(n \cdot 2^n)\)

总结

  这个题在写状态的时候需要考虑怎么转移的问题,这里线索第i个人要分配的任务需要看前i-1个人的分配情况,这样就建立起来了联系,把人放在第1维度,把对应的任务状态放在第2维度,f[i][j]表示前i个人完成任务j对应的最小时间。
  这里需要考虑预处理,是因为在转移的时候,需要知道某个任务组合如果都给一个人做的时候,需要总共多少时间,这个可以提前累加起来。同时在累加的时候,由于子集是从小到大的,对于某个大的sub,肯定是可以找到一个低位的j,ssum[sub] = ssum[sub^(1<<j)] + jobs[j]是肯定成立的,之后就能break,去找下一个sub,这样节省时间。
  给dp赋初值的时候,考虑到我们枚举人是需要往前看的,所以i要从1取,那么i=0的情况要初始化好,恰好i等于0时候就代表只派0号人去做,这个结果在ssum中刚好已经统计了。
  常规转移的时候,先枚举人,再枚举状态,再枚举状态的子集。这个题python不好过,需要一些小技巧优化,前两个只是技巧,后面考虑二分优化需要再彻底了解下。

标签:状态,需要,短时间,sub,mask,1723,枚举,子集,完成
From: https://www.cnblogs.com/zk-icewall/p/16831711.html

相关文章