《合唱团》算法解析(含思路解答示意图)【牛客网编程题——JAVA实现】

考点:动态规划】动态规划变形,难度指数上升

题目描述

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

输入描述

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

输出描述

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

《合唱团》算法解析(含思路解答示意图)【牛客网编程题——JAVA实现】_第1张图片

———————————————————————————————————————————————

以下解题思路整理来自牛客网讨论区

1. 题目分析

题目要求n各学生中选择k个,使这k个学生的能力值乘积最大。这是一个最优化的问题。另外,在优化过程中,提出了相邻两个学生的位置编号差不超过d的约束。

如果不用递归或者动态规划,问题很难入手,并且,限制条件d也需要对每一个进行约束,编程十分复杂

所以,解决的方法是采用动态规划(理由:1.求解的是最优化问题;2.可以分解为最优子结构)

2. 问题分解
  • 对该问题的分解是关键
    从n个学生中,选择k个,可以看成是:先从n个学生里选择最后1个,然后在剩下的里选择k-1个,并且让这1个和前k-1个满足约束条件

  • 数学描述
    为了能够编程实现,需要归纳出其递推公式,而在写递推公式之前,首先又需要对其进行数学描述

    记第k个人的位置为one,则可以用f[one][k]表示从n个人中选择k个的方案。然后,它的子问题,需要从one前面的left个人里面,选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].
    (1)学生能力数组记为arr[n+1],第i个学生的能力值为arr[i]
    (2)one表示最后一个人,其取值范围为[1,n];
    (3)left表示第k-1个人所处的位置,需要和第k个人的位置差不超过d,因此 max{k-1,one-d}<=left<=one-1

《合唱团》算法解析(含思路解答示意图)【牛客网编程题——JAVA实现】_第2张图片

    在n和k定了之后,需要求解出n个学生选择k个能力值乘积的最大值。因为能力值有正有负,所以有:
《合唱团》算法解析(含思路解答示意图)【牛客网编程题——JAVA实现】_第3张图片
    如上图所示:
    当one对应的学生能力值为正时,
        f[one][k] = max{f[left][k-1] * arr[i]}        (min{k-1,one-d}<=left<=one-1);
    当one对应的学生能力值为负时
        f[one][k] = max{g[left][k-1] * arr[i]}       (min{k-1,one-d}<=left<=one-1);
    此处g[][]是存储n个选k个能力值乘积的最小值数组

3. 编程实现

《合唱团》算法解析(含思路解答示意图)【牛客网编程题——JAVA实现】_第4张图片

    归纳以上思想,我们现在编程实现,可以先根据变量习惯进行变更,得到相应的递推公式。我们从curK = 1递推到curK = K

import java.util.*;

public class Main{
    
    public static void main(String[] args){
        
        //获取学生个数
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        
        int[] arr = new int[n + 1];
        //获取能力值
        for(int i = 1;i <= n; i++) {
            arr[i] = scanner.nextInt();
        }
        //获取k值
        int k = scanner.nextInt();
        //获取d值
        int d = scanner.nextInt();
        //创建最大值和最小值两个辅助数组,long数组存放数值,范围更大
        long[][] f = new long[n + 1][k + 1]; 
        long[][] g = new long[n + 1][k + 1];
        //初始化两个数组,即K=1的情况
        for(int i = 1; i <= n; i++) {
            f[i][1] = arr[i];
            g[i][1] = arr[i];
        }
        //从k = 2开始填充(遍历每一行)
        for(int curK = 2; curK <= k; curK++) {
            for(int curN = curK; curN <= n; curN++) {  //遍历每一列
                long tempMax = Long.MIN_VALUE;         //临时最大、最小值变量
                long tempMin = Long.MAX_VALUE;
                //根据left的两个边界条件进行遍历
                for(int left = Math.max(curK - 1, curN - d); left <= curN - 1; left++){
                    //根据所得的递推公式更新最小值 最大值
                    if(tempMax < Math.max(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN])) {
                        tempMax = Math.max(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN]);
                    }
                    
                    if(tempMin > Math.min(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN])) {
                        tempMin = Math.min(f[left][curK - 1] * arr[curN], g[left][curK - 1] * arr[curN]);
                    }
                }
                //更新最大值
                f[curN][curK] = tempMax;
                //更新最小值
                g[curN][curK] = tempMin;
            }
        }
        //确定了K值,要得到最大值,则遍历第K列,即搜索f[curN~n][K]  (curN >= k)
        long maxResult = Long.MIN_VALUE;
        for(int curN = k; curN <= n; curN++) {
            if(f[curN][k] > maxResult)
                maxResult = f[curN][k];
        }
        System.out.println(maxResult);
    }
}

思路补充

    这道题我一开始用传统个套路去做,一般的动态规划题目,中间使用的表的最后一个元素,dp[N][K]就是所求的结果。但这个题目不能这样,因为如果那样建表,子问题就成了“在前n个学生中,取k个,使乘积最大”——然而,本题目有额外的限制条件“相邻两个学生的位置编号的差不超过d”就没有办法代入递推公式了,因为子问题中本身并不包含位置信息。
    因此将子问题定为:在前n个学生中,取k个,第n个必取,使乘积最大。这样的话,最终的结果就不是dp[N][K],而是dp[..][K]这一列中最大的那个值。
    其次,求最大乘积比求最大和的问题要复杂许多。求最大和的话,子问题中也只需要求最大和就行了。但求最大乘积的时候,在子问题中,每一步需要求最大正积和最小负积。因为如果某学生的能力值为负数,乘以前面求得的最小负积,结果才是最大乘积。
    再次,这个问题最后算的数据比较大,已经不是int型能够包含的了,需要用long。

你可能感兴趣的:(算法与数据结构)