• 牛客网2017校招真题在线编程之合唱团问题——动态规划问题首秀


    先贴题目

    题目描述

    有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?

    输入描述:

    每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

    输出描述:

    输出一行表示最大的乘积。

    示例1

    输入

    3
    7 4 7
    2 50

    输出

    49

    动态规划之我见(适合小白,因为我就是小白,这是我做的第一个动态规划问题,查了很多资料,理解了很久,非小白可跳过)

    (1)首先,动态规划问题要求具有最优子结构,也就是说规模为n的问题的最优解,可以由规模为n-1(和/或更小规模)的子问题递推得到,其中子问题也是具有这种最优子结构的。因此动态规划问题得到的是全局最优解而不是局部最优解。
    (2)其次,从上面可以看到,动态规划最重要的是最优子结构,也就是这个递推表达式,找到这个递推表达式,后面就是编程实现的事情了。这个递推表达式有个学名叫做状态转移方程
    插一条:既然叫做状态转移方程,总得有“状态”吧,不然哪里的转移呢?我理解的“状态”,也就是“问题”!对,就是要求解的问题,只不过是一种表达而已,比如说合唱团问题中,n个人中找出k个人,使得能量值乘积最大,这就是我们所谓的状态,我们暂且把这个状态记作dp[n,k],
    这里dp是dynamic planning的缩写啦,“圈内人”都这么叫的哈哈哈。“状态”,在我们解决问题的时候往往是一维数组或者二维数组(这完全是自己的理解啦,没在哪本看到谁这么说过),究竟是一维数组还是二维数组,要根据具体问题具体分析,比如合唱团问题里的状态就是一个
    二维数组。还有一点就是,找状态的时候,先不要管约束条件,约束条件在最后寻找状态转移方程的时候满足其要求即可。
    (3)终于可以说第三点啦,有了状态,接下里就是找状态转移方程啦。也就是规模为n的问题怎样由规模为n-1(和/或更小规模)的子问题递推得到,找到这个递推关系式,找这个递推关系式方法也得具体问题具体分析,这一点可以参考上一篇博客(https://www.cnblogs.com/VanJing/p/9325158.html),
    里面介绍了几个动态规划的例子,仔细研读必有收获。

    总之,找准“状态”,找对“状态转移方程”是解决动态规划类问题的重点。


    合唱团题目相关
    1.题目分析 Ps.参考牛客网讨论
    题目要求n个学生中选择k个,使这k个学生的能力值乘积最大。这是一个最优化的问题。另外,在优化过程中,提出了相邻两个学生的位置编号差不超过d的约束。
    如果不用递归或者动态规划,问题很难入手,并且,限制条件d也需要对每一个进行约束,编程十分复杂。

    2.贴程序(Python3.5.2实现)
    别看程序就这么几行,但是不好理解。。。。
     1 #/user/bin/python
     2 #coding = utf-8
     3 n = int(input())
     4 arr = list(map(int, input().split()))
     5 K, D = map(int, input().split())
     6 
     7 fm = [[0] * n for _ in range(K)]
     8 fn = [[0] * n for _ in range(K)]
     9 res = 0
    10 
    11 for i in range(n):
    12     fm[0][i] = arr[i]
    13     fn[0][i] = arr[i]
    14 
    15 for i in range(n):
    16     for k in range(1,K):
    17         for j in range(i-1, max(0, i-D)-1, - 1):
    18             fm[k][i] = max(fm[k][i],max(fm[k-1][j] * arr[i], fn[k-1][j] * arr[i]))
    19             fn[k][i] = min(fn[k][i],min(fm[k-1][j] * arr[i], fn[k-1][j] * arr[i]))
    20     res = max(res, fm[K-1][i])
    21     
    22 print(res)
    23             
    24             
    2. 问题分析:
    从n个学生中,选择k个,根据“动态规划之我见”部分,这里状态定义为f[n][k],为了和后面分析以及程序一致,将状态写成f[k][n]

    因为能量值arr有正有负,最后要找能量值乘积的最大值,我们知道负负得正,所以要维护两个dp数组,一个存储最大,一个存储最小。
    定义fm[k][i]表示当选中了k个学生,并且以第i个学生为结尾,所产生的最大乘积;
    fn[k][i]表示当选中了k个学生,并且以第i个学生为结尾,所产生的最小乘积;
    所以fm[k=1..K][i=1..n],fn[k=1..K][i=1..n]为K*n的数组,程序6~7行就是初始化这两个数组。
    再分析一下fm和fn。其意义不是很好理解,具体可以在纸上画出一个K行n列的表格出来,第一行,表示从n个人(假设为1...n,其能量值为arr[1]...arr[n])中1个人,那么最大的能量值乘积自然是选中的这个人的能量值,因此无论是fm还是fn,第一行的值就是arr[i],
    这也就是程序11~13行干的事情。

    fm[k][i]表示第k个人选中arr[i]时能量乘积最大值,fn同理。(理解这一点非常非常重要)
    再看状态转移方程。状态转移方程,也就是子问题的求解。从n个学生中,选择k个,可以看成是:先从n个学生里选择第k个(假设arr[i]),然后在剩下的里(arr[i]的左边部分)选择k-1个,并且让第k个和前k-1个满足约束条件。根据上一行,第k个人选中arr[i]表示为fm[k][i],
    剩下k-1个人选中arr[j]表示为fm[k-1][j],(这里j需要满足约束条件,稍后再讨论)。如果arr[i]为正,则最大的能量值乘积为arr[i]*fm[k-1][j];如果arr[i]为负。则最大的能量值乘积为arr[i]*fn[k-1][j],无论arr[i]为正数还是负数,都是取arr[i]*fm[k-1][j]
    和arr[i]*fn[k-1][j]中最大的那个,因此可以统一表示成max(fm[k-1][j] * arr[i], fn[k-1][j] * arr[i])。fn同理。这一部分实现如15~19行(除掉17行)所示。
    其中,第18行为什么要在max(fm[k-1][j] * arr[i], fn[k-1][j] * arr[i])外面套了一个与fm[k][i]比较取最大,第19行为什么要在min(fn[k-1][j] * arr[i], fn[k-1][j] * arr[i])外面套了一个与fm[k][i]比较取最小,这一点尚未思考清楚。也不想在这一点
    上太纠结,欢迎讨论。

    再看约束条件,约束条件要求相邻两个学生的位置编号的差不超过d,这一部分代码实现主要体现在第17行。arr[i]的编号为i(Python中索引为i-1),因此剩下k-1个人里,只能从编号为i-1(Python里应该为i-2)向左数d个位置,在i-1-D到i的位置(不包含i)里找第k-1个人
    但是如果D太大,总不能在i<1的部分找人吧,因此需要取max(0,i-D),Ps.程序里是max(0,i-D)-1,因为Python里range取左开右闭,为了能取到边界条件0或者i-D,因此加了个-1.
    再PS.动态规划里的边界条件处理也是非常重要的,需要尤其注意,不然非常容易出错。
    说到边界条件,回看第16行!range(1,K),为什么不是range(K)呢?因为range(K)时i会从0开始迭代,会掩盖掉选择选中一个人的情况(也就是掩盖掉11~13行结果),因此这些细节一定要注意啊,不然很难做到程序百分百正确。


    还有一点,打印结果!打印部分不仅仅是第22行,还有第20行,max(res,fm[K-1][i]),这是在第一层循环里的哦,但是不用在第二层循环里,不想细说了,自己理解去吧,不是很难。


    ok,除了其中的一个小bug,基本理解完了。真的花了我n多时间啊啊!!!总体而言还是看了很多大神的代码和分析,才把这个完完全全理解掉的。对于我这个小白,最终能看到下面这个图,真的蛮开心!

    写在最最最后:第一次自己敲字写博客,排版什么的自己真的不是很擅长,也没那个闲工夫去调整,各位看官凑合着看吧,谢谢!

    
    
    
    
  • 相关阅读:
    时区处理
    C#视频取帧图
    【C#】C#获取文件夹下的所有文件
    利用web of science做论文综述
    C# HTML解析工具HtmlAgilityPack使用实例(一)
    C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子)
    win10文件夹或文件已在另一程序中打开
    指定网卡IP进行ping操作
    C# 使用WebClient时,在多网卡IP时,指定IP发送Web请求
    C# 消息队列
  • 原文地址:https://www.cnblogs.com/VanJing/p/9332630.html
Copyright © 2020-2023  润新知