合唱团问题(dp)

本文通过一道关于学生能力值乘积最大化的编程题,详细解析了动态规划的解题步骤和核心思想,包括如何将问题分解为子问题、确定状态及其转移方程,并给出了一段完整的Java实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、

题目描述

有 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



2、参考了他人的代码,反正是遇到dp问题,我就蒙圈....所以决定遇到的都好好分析并记录下来

code:ac

package schooloffer17;

import java.util.Scanner;

/**
 * Created by caoxiaohong on 17/10/30.
 * 有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生...
 * 动态规划
 */
public class Chorus {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        int n;
        int k,d;
        while (scanner.hasNextInt()){
            n=scanner.nextInt();//n个学生
            int[] values=new int[n+1];
            for(int i=0;i<n;i++){ //输入各个学生的值
                values[i+1]=scanner.nextInt();
            }
            k=scanner.nextInt();//选定学生的个数
            d=scanner.nextInt();//相邻学生的之间的最大间隔

            /**动态规划:为什么要有两个数组呢?因为学生的价值可能为负数,那么就要有一个数组存最大值,一个存最小值.如果学生价值为负数,
            那么如果乘以一个不管是不是负数的最小值,才能得到最大值.这就是最小值数组存在的意义.**/
            //因为乘积结果最大值为:Math.pow(50,10)=Math.pow(5,10)*Math.pow(10,10),所以数组类型采用long

            long[][] max=new long[n+1][k+1];//max[i][j]:i为组成j个人的最后一个人
            long[][] min=new long[n+1][k+1];//min[i][j]:i为组成j个人的最后一个人
            //显然max[i][0],min[i][0]=0,所以第1列不用初始化
            //同理,第1行不用初始化.
            //但是第2列需要初始化
            for(int i=1;i<n+1;i++){
                max[i][1]=values[i];
                min[i][1]=values[i];
            }

            //dp过程:
            for(int j=2;j<k+1;j++){
                for(int i=j;i<n+1;i++){
                    long max0=Integer.MIN_VALUE;//记录下面for循环中出现的最大值,然后赋值给max[i][j]
                    long min0=Integer.MAX_VALUE;//记录下面for循环出现的最小值,然后赋值给min[i][j]
                   //求解,当j,i值确定时,最大的分割点
                    for(int left=Math.max(j-1,i-d);left<i;left++){
                        if(max0<Math.max(max[left][j-1]*values[i],min[left][j-1]*values[i])){
                            max0=Math.max(max[left][j-1]*values[i],min[left][j-1]*values[i]);
                        }
                        if(min0>Math.min(max[left][j-1]*values[i],min[left][j-1]*values[i])){
                            min0=Math.min(max[left][j-1]*values[i],min[left][j-1]*values[i]);
                        }
                    }
                    max[i][j]=max0;
                    min[i][j]=min0;
                }
            }
            //根据对max[i][j]的定义:第i个人作为选择的j个人的最后一个人,所以此时需要对max第k+1列进行遍历,找出最大值
            long result=Integer.MIN_VALUE;
            for(int i=k;i<n+1;i++){ //显然,第k+1列,从第k+1行开始,值才不为0;所以i从k开始.
                if(result<max[i][k])
                    result=max[i][k];
            }
            System.out.println(result);
        }
    }
}



3、再看dp分析:http://blog.youkuaiyun.com/baidu_28312631/article/details/47418773

3.1、递归到动规的一般转化方法

    递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始, 逐步填充数组,相当于计算递归函数值的逆过程。


3.2、动规解题的一般思路

(1)将原问题分解为子问题

     把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决。

     子问题的解一旦求出就会被保存,所以每个子问题只需求解一次。

(2)确定状态

     在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。

     所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 

     整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。

(3)确定一些初始状态(边界状态)的值

(4)确定状态转移方程

     定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。


3.3、能用动规解决的问题的特点

(1)问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。

(2)无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值