合唱团问题(dp)

来源:互联网 发布:西安爱知 地址 编辑:程序博客网 时间:2024/05/22 11:44

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

输入

37 4 72 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.csdn.net/baidu_28312631/article/details/47418773

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

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


3.2、动规解题的一般思路

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

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

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

(2)确定状态

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

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

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

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

(4)确定状态转移方程

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


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

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

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


原创粉丝点击