题目描述
有 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-1个是最大的,那么比较能参选第k个的所有元素和前k-1个的乘积,就能得到最大
但是我们注意到,学生的能力值有正有负,所以我们需要维护两个数组,分别保存最大值和最小值
class student{
public long ability;
public student(long ability){
this.ability = ability;
}
}
public class 合唱团 {
public static long MAX_VALUE = Long.MAX_VALUE;
public static long MIN_VALUE = Long.MIN_VALUE;
public static void main(String[] args) {
long Max = getMax();
System.out.println(Max);
}
public static long getMax(){
List<student> lits = new ArrayList<>();
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long abil;
//输入数据
for(int i = 0;i<n;i++){
abil = in.nextLong();
student s = new student(abil);
lits.add(s);
}
int k = in.nextInt();
int d = in.nextInt();
//维护的两个最大值和最小值的数组,k表示选择几个学生,n表示第k个选取的学生
//所以fMax[k][n]的乘积就是选择k个学生且第k个学生是n的时候的最大值
long [][] fMax = new long[k][n];
long [][] fMin = new long[k][n];
long Max = MIN_VALUE;
for(int i = 0;i<k;i++){
for(int j =0;j<n;j++){
fMax[i][j] = MIN_VALUE;
fMin[i][j] = MAX_VALUE;
}
}
//初始化选取一个的时候的最大最小值
for(int i = 0 ;i<n;i++){
fMax[0][i] = lits.get(i).ability;
fMin[0][i] = lits.get(i).ability;
}
for(int i = 1 ;i<k;i++){
for(int j = i;j<n;j++){
long max = MIN_VALUE;
long min = MAX_VALUE;
long temp;
//在j的d范围内可以选取的前i-1个的最大值
//因为乘法交换律,所以每次只考虑当前元素的左侧有效范围
//Math.max(i-1, j-d)的意思是如果左侧的元素个数大于等于d,那么可以定位到,如果小于,就从i-1开始,至少左侧有i-1个
for(int t = Math.max(i-1, j-d);t<j;t++){
temp = Math.max(fMax[i-1][t]*lits.get(j).ability, fMin[i-1][t]*lits.get(j).ability);
if(max<temp){
max = temp;
}
temp = Math.min(fMax[i-1][t]*lits.get(j).ability, fMin[i-1][t]*lits.get(j).ability);
if(min>temp){
min=temp;
}
}
fMax[i][j] = max;
fMin[i][j] = min;
}
}
for(int i = k-1 ;i<n;i++){
if(Max<fMax[k-1][i]){
Max = fMax[k-1][i];
}
}
return Max;
}
}