zoukankan      html  css  js  c++  java
  • 详解数组分段和最大值最小问题(最小m段和问题)

    数组分段和最大值最小问题(最小m段和问题)

    问题描述

    给定n个整数组成的序列,现在要求将序列分割为m段,每段子序列中的数在原序列中连续排列。如何分割才能使这m段子序列的和的最大值达到最小?

    清洁工:假设有n个房间,清洁每个房间耗时用一个数组表示,10、20、30、40、50、60、70、80、90,安排m个清洁工,将连续的房间分成m份,每部分耗时求和,其最大值为此种分法的总耗时。求最快的耗时是多少。例如3个清洁工的话,10 20 30 40 50 | 60 70 | 80 90,此时是最快的,耗时为170。

    装桶:把数据按顺序装入桶中,m即是给定的桶数,问桶的容量至少应该为多少才能恰好把这些数装入m个桶中

    思路

    先考虑最简单的情况,假设有 (1) 个数,只有一个桶,(m=1):至少需要容量为该数的值;

    • 如果 (n) 个数,只有一个桶,(m=1):至少需要容量为 (n) 个数之和;
    • 如果 (2) 个数,两个桶,(m=2):至少需要容量为两数之间最大值
    • 如果 (t) 个数,两个桶,(m=2) 呢?

    我们将 (t) 个数划分为两份,随机选一个划分位置 (k)

    10, 20, 30, 40, | 50, 60, 70, 80, 90 
    

    就变成两部分: (k) 个数分到 1 个桶;(t-k) 个数分到一个桶

    最少所需容量为 (f(t,2) = min{max[f(k,1),f(t-k,1)]},1leq k < t),其中 (f(t-k,1)) 表示后 (t-k) 个数之和,表示为(f(t-k,1) = f(t,1)-f(k,1))

    • 现在考虑 (n) 个数,(m) 个桶:

    我们同样将 (n) 个数划分为两份,即前 (m-1) 个桶和最后 (1) 个桶,随机选一个划分位置 (k)

    [f(n,m) = min{max[f(k,m-1),f(n-k,1)]},1leq k < n ]

    可以使用递归求解了,但是太耗时。我们使用动态规划填表就可以搞定了:

    [f(i,j) = min{max[f(k,j-1),f(i,1)-f(k,1)]},1leq k < n ]

    可以用行表示对应的数字,列表示桶的数目,对1,2,3,4,5,两个桶:

    桶1 桶2
    1 1 1
    2 3 2
    3 6 3
    4 10 7
    5 15 9

    coding:

    #---python
    #动态规划
    # dp[i][j] = min(max(dp[k][j-1],dp[i][1]-dp[k][1]))
    def dp_minMsum(nums,m):
        n = len(nums)
        dp = [[float('inf')]*(m+1) for _ in range(n+1)]
        
        #初始化
        for i in range(1,n+1):#只有一个桶
            dp[i][1] = sum(nums[:i])
            
        for j in range(1,m+1):#只有一个数
            dp[1][j] = nums[0]
            
        for i in range(2,n+1):
            for j in range(2,m+1):
                for k in range(1,i+1):
                    dp[i][j] = min(dp[i][j],max(dp[k][j-1],dp[i][1]-dp[k][1]))
                               
        return dp[-1][-1]
    

    测试下:

    >>> nums = [5,3,2,4,1]
    >>> m = 2
    >>> dp_minMsum(nums,m)
    8
    
    >>> nums = [10, 20, 30, 40, 50, 60, 70, 80, 90]   
    >>> m=3
    >>> dp_minMsum(nums,m)
    170
    

    复杂度

    时间复杂度:(O(MN^2))

    空间复杂度:(O(NM))

    二分查找优化

    给定当前的桶容量,可以计算出需要的最少桶数:

    如果需要的桶数量大于给定的桶数量k,说明桶容量太小了,增大容量;

    如果计算需要的桶数量小于等于k,说明桶容量可能大了(也可能正好是要找的最小桶容量)

    • 在给定容量时,求需要桶数
    • 二分搜索桶容量

    coding:

    #---python
    #根据桶容量进行二分查找对应的桶数
    def bs_minMsum(nums,m):
        low = max(nums)#容量下界 最大元素
        high = sum(nums)#容量上界 元素和
        while low < high:
            mid = low+(high-low)//2
            buckets = get_buckets(nums,mid)
            if buckets <= m:#该容量下桶数 小于 给定桶数。减小容量
                high = mid
            else:           #桶数 大于 给定桶数。增大容量
                low = mid +1
    
        return low
    
    def get_buckets(nums,volume):
        buckets = 1
        ans = 0
        for num in nums:
            ans += num
            if ans > volume:
                buckets += 1
                ans = num
        return buckets
    

    复杂度

    时间复杂度:(O(Nlog(sum n))),(sum n) 为给定数组的元素和

    空间复杂度:(O(1))

  • 相关阅读:
    python之路_保留原搜索页面条件
    python之路_面向对象相关知识点
    2-SAT习题讲解
    圆方树浅谈
    莫队浅谈&题目讲解
    矩阵树定理浅谈
    动态点分治浅谈
    线性基浅谈
    模板—费用流(最小费用最大流)
    模板—数学—Exgcd
  • 原文地址:https://www.cnblogs.com/gongyanzh/p/12803616.html
Copyright © 2011-2022 走看看