动态规划——最小化所有子序列中最大值的和

问题描述

给定一长度为 N N N的整数序列 ( a 1 , a 2 , . . . , a N ) (a_1,a_2,...,a_N) (a1,a2,...,aN),将其划分成多个子序列(此问题中子序列是连续的一段整数),满足每个子序列中整数的和不大于一个数 B B B,设计一种划分方法,最小化所有子序列中最大值的和。说明其具有最优子结构及子问题重叠性质。

例如:序列长度为8的整数序列 ( 2 , 2 , 2 , 8 , 1 , 8 , 2 , 1 ) (2,2,2,8,1,8,2,1) (2,2,2,8,1,8,2,1) B = 17 B=17 B=17,可将其划分成三个子序列 ( 2 , 2 , 2 ) (2,2,2) (2,2,2) ( 8 , 1 , 8 ) (8,1,8) (8,1,8)以及 ( 2 , 1 ) (2,1) (2,1),则可满足每个子序列中整数和不大于17,所有子序列中最大值的和12为最终结果。

算法思路

这个题目看起来非常的绕口,非常的复杂,要求找到最小的子序列中最大值的和,并且还要求子序列中的整数和不大于 B B B。因为知道要使用动态规划的思想来解决,而动态规划一般都会使用一个数组来存储过程中的结果,当算法执行完毕的时候,该数组的最后一个元素就会是我们想要的答案。所以我们可以先不用管具体步骤是什么,使用数组 n u m s nums nums来表示给定的长度为 N N N的整数序列,下标从1开始,设置一个数组 s u m [ i ] sum[i] sum[i]来表示从第1个数到第 i i i个数的最小子序列中最大值的和,很显然 s u m [ 0 ] = 0 sum[0]=0 sum[0]=0,那么我们最后的结果就是要求 s u m [ N ] sum[N] sum[N],接下来仍然是使用题目所给的最基础的例子开始一步一步找规律。

  • N = 1 N=1 N=1,原始序列中只有一个元素,则 s u m [ N ] = n u m s [ N ] = 2 sum[N]=nums[N]=2 sum[N]=nums[N]=2
  • N = 2 N=2 N=2,这时候有两种情况 ( 2 , 2 ) (2,2) (2,2) ( 2 ) , ( 2 ) (2),(2) (2),(2),这时候 s u m [ N ] = m i n ( 2 , 2 + 2 ) = 2 sum[N]=min(2,2+2)=2 sum[N]=min(2,2+2)=2
  • N = 3 N=3 N=3,这时候有四种情况 ( 2 , 2 , 2 ) (2,2,2) (2,2,2) ( 2 ) , ( 2 , 2 ) (2),(2,2) (2),(2,2) ( 2 , 2 ) , ( 2 ) (2,2),(2) (2,2),(2) ( 2 ) , ( 2 ) , ( 2 ) (2),(2),(2) (2),(2),(2),这时候 s u m [ N ] = m i n ( 2 , 2 + 2 , 2 + 2 , 2 + 2 + 2 ) = 2 sum[N]=min(2,2+2,2+2,2+2+2)=2 sum[N]=min(2,2+2,2+2,2+2+2)=2

从上面的例子中可以看出来,这道题其实和前面的第一题有一点像,都是类似于有一个游标 p p p在整个序列中进行游走, p p p所指的位置一刀两断,将这个序列划分成前后两个子序列,就像第二种情况,一开始游标 p p p为0,则游标的右边其实是数组整体,则他的最大值就是数组元素的最大值,当 p p p走到位置1的时候,数组分为两部分,游标前面那部分的最大值可以通过 s u m [ 1 ] sum[1] sum[1]计算出来,而后面则为剩余元素的最大值,很显然这里只有一个元素为2,然后取这两种最大值中最小的那一个。情况3也是类似的, p p p从0开始,一直走到2,这里用形式化的语言来描述就是 s u m [ 3 ] = m i n ( s u m [ 0 ] + m a x ( n u m s [ 1 ] , n u m s [ 2 ] , n u m s [ 3 ] ) , s u m [ 1 ] + m a x ( n u m s [ 2 ] , n u m s [ 3 ] ) , s u m [ 2 ] + m a x ( n u m [ 3 ] ) ) sum[3]=min(sum[0]+max(nums[1],nums[2],nums[3]), sum[1]+max(nums[2],nums[3]), sum[2]+max(num[3])) sum[3]=min(sum[0]+max(nums[1],nums[2],nums[3]),sum[1]+max(nums[2],nums[3]),sum[2]+max(num[3]))
由于游标一直游走在 [ 0 , i − 1 ] [0,i-1] [0,i1]内,所以当我们求 s u m [ i ] sum[i] sum[i]的时候,所有的式子都是已知的,在这个的基础上,我们需要保证每个子序列中的整数和不大于17,即 m a x ( n u m s [ p + 1 ] , . . . , n u m s [ i ] ) ≤ B max(nums[p+1],...,nums[i])\leq B max(nums[p+1],...,nums[i])B
所以最终我们可以得出这样的转移表达式:
s u m [ i ] = m i n ( s u m [ p ] + m a x ( n u m s [ p + 1 ] , . . . , n u m s [ i ] ) ) , 0 ≤ p < i , ∑ k = p + 1 i n u m s [ k ] ≤ B sum[i]=min(sum[p]+max(nums[p+1],...,nums[i])),0\leq p<i,\sum_{k=p+1}^{i}nums[k]\leq B sum[i]=min(sum[p]+max(nums[p+1],...,nums[i])),0p<i,k=p+1inums[k]B

子问题性质说明

一般来讲一个题目能够使用动态规划算法,那么他应该具备两个条件:

  • 最优子结构:当一个问题的最优解包含了子问题的最优解时,称这个问题具有最优子结构。
  • 重叠子问题:在问题的求解过程中,很多子问题的解将被多次使用。

有了上面分析的递推式,其实就能比较容易地发现该问题具备这两个性质。一般来讲使用反证法来证明其具有最优子结构,下面进行简要证明:

由于 s u m [ i ] sum[i] sum[i]是通过 s u m [ p ] sum[p] sum[p]转移得到的,若 s u m [ i ] sum[i] sum[i]是最优值,则 s u m [ p ] sum[p] sum[p]也肯定是最优值。可以通过反证法证明:
若存在
s u m ′ [ p ] < s u m [ p ] sum^{\prime}[p]<sum[p] sum[p]<sum[p]那么必定存在
s u m ′ [ i ] = m i n ( s u m ′ [ p ] + m a x ( n u m s [ p + 1 ] , . . . , n u m [ i ] ) ) < s u m [ i ] sum^{\prime}[i]=min(sum{\prime}[p]+max(nums[p+1],...,num[i]))<sum[i] sum[i]=min(sum[p]+max(nums[p+1],...,num[i]))<sum[i] s u m [ i ] sum[i] sum[i]不为最优值,与假设矛盾。因此该问题具有最优子结构性质。

重叠子问题的情况也非常容易说明, p p p为游标,游走在 [ 0 , i ) [0,i) [0,i)之间,子问题构成树的形状,树的每一层的每一个子问题,p都会在 [ 1 , i ) [1,i) [1,i)之间游走一次,所以必定会存在若干个 s u m [ p ] sum[p] sum[p]是重复的子问题,比如说 s u m [ 3 ] sum[3] sum[3] s u m [ 2 ] sum[2] sum[2]都包含 s u m [ 1 ] sum[1] sum[1]这个子问题。

算法实现

int split_subseq(b, n, nums[]) {
    
    
    sum(n + 1, INT_MAX);  // 存放从第1个数到第n个数的序列中的最小子序列中最大值的和
    sum[0] = 0;
    sum[1] = nums[1];  // 前两个值赋值

    for i from 2 to n:  // 自底向上求
        for p from 0 to i - 1:                    // 游标来回动
            int max_val = INT_MIN, add_sum = 0;
            for k from p + 1 to i:                // 找出来后面这个子序列的最大值
                add_sum += nums[k];  // 计算累加和
                max_val = max(max_val, nums[k]);  // 找出最大值
            end for;
            if add_sum < b:
		        // 状态转移方程
		        sum[i] = min(sum[i], sum[p] + max_val);
        end for;
    end for;
    return sum[n];
}

猜你喜欢

转载自blog.csdn.net/qq_41983842/article/details/123935462