美文网首页动态规划
动态规划_实战2

动态规划_实战2

作者: 小碧小琳 | 来源:发表于2018-08-29 21:19 被阅读16次

    网易2017年合唱团

    做题经验:
    对于一个复杂的题,若是附加条件太多,不能一步到位的解决,可以逐渐的减少附加条件,也就是逐渐的减少参数,等有思路了,再把参数添加进去。

    对于这题,作者就是这么想的,一开始,把附加条件d去掉,就是一个很典型的DP问题,然后建模, 递归实现,DP算法实现,最后再把d添加进去。

    一、递归实现

    这里需要提醒的是,因为能力值有正有负,在下一个值不知道正负性的情况下,是不能确定应该返回哪一个值的,很可能这一次的最小值乘以负数就变成最大值了。
    因此不是每次返回一个值,而是每次返回两个值。一个最大值,一个最小值。

    import sys
    
    n =  36
    ability = [7,-15,31,49,-44,35,44,-47,-23,15,-11,10,-21,10,-13,0,-20,-36,22,-13,-39,-39,-31,-13,-27,-43,-6,40,5,-47,35,-8,24,-31,-24,-1]
    k,d = 2, 31
    
    def chorus(n, ability, k):
        if k == 1:
            return [max(ability),min(ability)]
        if k == n:
            result = 1
            for i in range(n):
                result = result * ability[i]
            return [result,result]
    
        #选中最后一个同学,会得到最大值与最小值
        last_list = chorus(n-1,ability[0:n-1],k-1)
        #未选中最后一个同学,得到最大值与最小值
        no_last_list = chorus(n-1,ability[0:n-1],k)
        #不能认为,index为0的就是max,因为ability[-1]的不确定性,所以可能会不断改变
        #因此需要再每一步都判断一下,得到最大或最小值
        return [max(last_list[0]*ability[-1],
                    last_list[1]*ability[-1],
                    max(no_last_list)),
                min(last_list[0] * ability[-1],
                    last_list[1] * ability[-1],
                    min(no_last_list))]
    
    print(chorus(n, ability, k))
    

    二、DP实现(不加d)

    这里因为只有两个参数,n和k,分析状态转移方程,可以知道,k一定时,f[k][n_id]的每个状态都可以由k-1的每个状态决定,因此这里只需要维护两个一维数组即可。

    代码实现:

    
    n =  36
    ability = [7,-15,31,49,-44,35,44,-47,-23,15,-11,10,-21,10,-13,0,-20,-36,22,-13,-39,-39,-31,-13,-27,-43,-6,40,5,-47,35,-8,24,-31,-24,-1]
    k,d = 3, 31
    
    
    def chorus(n, ability, k):
        pre_res_max = []
        pre_res_min = []
        if k == 1:
            return max(ability)
        else:
            for i in range(n):
                pre_res_max.append(max(ability[0:i+1]))
                pre_res_min.append(min(ability[0:i+1]))
    
    
        for k_id in range(2,k+1):
            after_res_max = []
            after_res_min = []
            for n_id in range(n):
                if k_id > n_id:
                    res = 1
                    for i in range(n_id+1):
                        res = res * ability[i]
                    after_res_max.append(res)
                    after_res_min.append(res)
                else:
                    #对于这一点尤其要注意,用哪个元素,千万不能错了。
                    #这里要是用n_id,就是49*49了,元素就重复了。
                    max1 = pre_res_max[n_id-1]*ability[n_id]
                    min1 = pre_res_min[n_id-1] * ability[n_id]
                    max2 = after_res_max[n_id-1]
                    min2 = after_res_min[n_id-1]
                    result_list = [max1,max2,min1,min2]
                    after_res_max.append(max(result_list))
                    after_res_min.append(min(result_list))
    
            pre_res_max = after_res_max
            pre_res_min = after_res_min
    
    
        return max(pre_res_max)
    
    print(chorus(n, ability, k))
    
    

    三、DP实现(加d)

    因为这里有三个参数n,k,d,需要好好定义一下需要保存的数组。

    f[k][i]代表的是,选择k个学生并且以第i个学生作为结尾时的最大(或最小)乘积。

    如何定义需要保存的数组,也是一门玄学?

    基本上,是根据动态转移方程中的参数的值,来确定需要保存的东西。
    比如国王与金矿问题中,下一行的某个状态,由上一行的某两个状态决定,因为需要维护一个一维数组,用来保存上一行的最优值。
    然而在LeetCode第62题中,因为状态转移方程是dp[i][j] = dp[i - 1][j] + dp[i][j - 1],当前状态由上一行的第j列与当前行的前一个决定,所以只需要保存两个数据即可。

    要根据状态转移方程确定需要保存的东西。

    因此在k等于1时,初始化数组时,需要注意初始化的正确性。

    建模分析

    根据f[k][i]的定义,我们可以得到最优子结构与状态转移方程。
    一定要举例说明

    • 1、例如f[3][6],已经选择第7个同学作为结尾,那么ability的第7个值就作为乘积,若是前一个同学选择第6个,那么就是f[2][5] * ability[6],这是其中一个最优子结构,同理,若是倒数第二个同学是第5个,那么就是f[2][4] * ability[6]。

    • 2、理论上,对于n_id,有n_id-1个子结构。因此应该在range(0,n_id)中遍历每个序号作为倒数第二个同学,在遍历的就结果中选择最大最小值。

    • 3、又因为这里加了限制,倒数第二个同学的编号与最后一个编号不能超过d。
      于是,修改循环的取值,若n_id大于d,则从0开始,小于d就从 n_id - d开始
      改为range(max(0, n_id - d), n_id)。
      这里真的是根据题意得到的一个小技巧。

    代码实现:

    n = int(input())
    ability = [int(x) for x in input().split()]
    k, d = [int(x) for x in input().split()]
    
    
    def chorus_dp2(n,ability,k,d):
        # 初始化两个数组,分别存储最大值最小值
        # 其中,f[k][i]代表的是,选择k个学生并且以第i个学生作为结尾时的最大(或最小)乘积
        f_max = []
        f_min = []
        for k_id in range(k):
            f_max.append([])
            f_min.append([])
            for n_id in range(n):
                if k_id == 0:
                    #在k等于1 的时候,根据f[k][i]的定义可知,第i个值就应该是ability[i]
                    f_max[k_id].append(ability[n_id])
                    f_min[k_id].append(ability[n_id])
                else:
                    f_max[k_id].append(0)
                    f_min[k_id].append(0)
    
        for k_id in range(1,k):
                res_mid_max = []
                res_mid_min = []
                for j in range(max(0, n_id - d), n_id):
                    res_mid_max.append(max(f_max[k_id-1][j] * ability[n_id],
                                           f_min[k_id-1][j] * ability[n_id]))
                    res_mid_min.append(min(f_max[k_id-1][j] * ability[n_id],
                                           f_min[k_id-1][j] * ability[n_id]))
                f_max[k_id][n_id] = max(res_mid_max)
                f_min[k_id][n_id] = min(res_mid_min)
    
        return f_max
    
    print(max(chorus_dp2(n,ability,k,d)[k-1]))
    
    
    #这段代码并没有考虑k等于n的情况。有bug。
    #题目中也没有给k大于n时应该怎么办,是不是允许重复的。题目这一点也出的不好。
    

    相关文章

      网友评论

        本文标题:动态规划_实战2

        本文链接:https://www.haomeiwen.com/subject/kixlwftx.html