1、
题目描述
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。
输入
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)无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。