题目描述
有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述:
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述:
输出一行表示最大的乘积。
题目分析:
从n个学生中选择k个,可以看成是先选出第k个学生,再从剩下学生中选出k个。
记第k个人的位置为start,则可以用f[start][k]表示从n个人中选择k个的方案。然后,它的子问题,需要从start前面的left个人里面,选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].
学生能力数组记为arr[n+1],第i个学生的能力值为arr[i]
start表示最后一个人,其取值范围为[1,n];
left表示第k-1个人所处的位置,需要和第k个人的位置差不超过d,因此
max{k-1,one-d}<=left<=one-1
在n和k定了之后,需要求解出n个学生选择k个能力值乘积的最大值。因为能力值有正有负,所以
当start对应的学生能力值为正时,求其最大值
f[start][k] = max{f[left][k-1]*arr[i],(min{k-1,start-d}<=left<=start-1)};
当start对应的学生能力值为负时,求最小值
f[start][k] = max{g[left][k-1]*arr[i],(min{k-1,start-d}<=left<=start-1)};
此处g[][]是存储n个选k个能力值乘积的最小值数组
代码实现
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
while(sc.hasNextInt()){
//总人数
int n=sc.nextInt();
//能力值数组
int[] arr=new int[n+1];
for(int i=1;i<n+1;i++){
arr[i]=sc.nextInt();
}
//选择的学生个数
int kk=sc.nextInt();
//最大间距
int d=sc.nextInt();
//规划数组
//最大值数组
long[][] f=new long[n+1][kk+1];
//最小值数组
long[][] g=new long[n+1][kk+1];
//初始化选择学生个数为1时的情况
for(int i=1;i<n+1;i++){
f[i][1]=arr[i];
g[i][1]=arr[i];
}
//从下向上推导
for(int k=2;k<=kk;k++){
for(int start=k;start<=n;start++){
//求解one,k定的时候最大的分割点
long temMax=Long.MIN_VALUE;
long temMin=Long.MAX_VALUE;
for(int left=Math.max(k-1,start-d);left<=start-1;left++){
if(temMax<Math.max(f[left][k-1]*arr[start],g[left][k-1]*arr[start])){
temMax=Math.max(f[left][k-1]*arr[start],g[left][k-1]*arr[start]);
}
if(temMin>Math.min(f[left][k-1]*arr[start],g[left][k-1]*arr[start])){
temMin=Math.min(f[left][k-1]*arr[start],g[left][k-1]*arr[start]);
}
}
f[start][k]=temMax;
g[start][k]=temMin;
}
}
//n选k最大的需要从最后一个最大的位置选
long res=Long.MIN_VALUE;
for(int start=kk;start<=n;start++){
if(res<f[start][kk]){
res=f[start][kk];
}
}
System.out.println(res);
}
}
}