有 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
分析:首先这是一个最优化问题【因为选择的k个学生不同,最后的结果也不一样,题目要求求最大,就需要从这些结构中选出最大的那个】
所以我们可以先确定 选择第k个学生的所有可能位置,得到这些所有可能位置下的最大乘积,然后从这些最大乘积中再选出最大的那个
那么如何确定第k个学生的所有可能位置呢?
我们只要保证 从前往后选择时,至少有k-1个学生可选,那么剩余的位置就是第k个学生的所有可能位置了
如何求解:计算出选择k-1个人的最大乘积,然后乘以当前第k个学生的能力值,即可得到选择k个学生的最大乘积
那么如何确定第k-1个学生的所有可能位置呢?
这里有两点需要注意:
1.保证 从前往右选择时,至少有k-2个学生可选
2.第k-1个学生跟第k个学生位置编号差不超过d
所以在找第k-1个学生所有可能位置的初始值时,我们需要取的是上述两种情况下的较大值,这样两种情况才能同时满足
此时又有一个问题值得注意:学生的能力值有正有负,那么
在第k个学生能力值为正数的情况下,求选择k个学生的最大乘积时,直接用已得到的选择k-1个学生的最大乘积乘以第k个学生的能力值便可得到
在第k个学生能力值为负数的情况下,求选择k个学生的最大乘积时,我们需要注意的是用k-1个学生的最小乘积乘以第k个学生的能力值,因为负的越少才越大
学生能力数组记为arr[n+1],第i个学生的能力值为arr[i]
用动态规划求解:
定义状态:f[one][k]:从n个人中选择k个人时的最大乘积,其中one表示第k个人【选择的最后一个人】的位置 ,k表示包括这个人,一共有k个人
g[one][k]:从n个人中选择k个人时的最小乘积,其中one表示第k个人【选择的最后一个人】的位置,k表示包括这个人,一共有k个人
状态转移方程:f[one][k] = max(f[one][k],max(f[left][k-1]*arr[one],g[left][k-1]*arr[one]))
g[one][k] = min(f[one][k],min(f[left][k-1]*arr[one],g[left][k-1]*arr[one]))
初始状态:f[one][1]=g[one][1]=arr[one] one从1到n 表示选择1个学生时,当前的最大/最小乘积就是该学生的能力值
返回结果:max(f[one][k]) one从k到n
import java.util.Scanner;
public class Chorus {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
//总人数
int n = sc.nextInt();
//学生能力值数组,第i个人直接对应arr[i]
int[] arr = new int[n + 1];
//初始化
for (int i = 1; i <= n; i++) {//人直接对应坐标
arr[i] = sc.nextInt();
}
//选择的学生数
int kk = sc.nextInt();
//间距
int dd = sc.nextInt();
output(n,arr,kk,dd);
}
}
private static void output(int n, int[] arr, int kk, int dd) {
/*
* 递推的时候,以f[one][k]的形式表示
* 其中:one表示最后一个人的位置,k为包括这个人,一共有k个人
* 原问题和子问题的关系:f[one][k]=max{f[left][k-1]*arr[one],g[left][k-1]*arr[one]}
*/
//规划数组
long[][] f = new long[n + 1][kk + 1];//人直接对应坐标,n和kk都要+1
long[][] g = new long[n + 1][kk + 1];
//初始化k=1的情况
for (int one = 1; one <= n; one++) {
f[one][1] = arr[one];
g[one][1] = arr[one];
}
//自底向上递推
for (int k = 2; k <= kk; k++) {
for (int one = k; one <= n; one++) { //选k个人时,第k个人所有可能的位置
f[one][k] = Long.MIN_VALUE;
g[one][k] = Long.MAX_VALUE;
for (int left = Math.max(k - 1, one - dd); left <= one - 1; left++) { //第k-1个人的所有可能位置【既要保证间隔满足条件,又要保证第k-1个人前面还有k-2个人可选】
f[one][k] = Math.max(f[one][k], Math.max(f[left][k - 1] * arr[one], g[left][k - 1] * arr[one]));
g[one][k] = Math.min(g[one][k], Math.min(f[left][k - 1] * arr[one], g[left][k - 1] * arr[one]));
}
//此时第三个for循环遍历完,就能确定 当第k个人位置为one【one从k遍历到n】,且共选择k个人【k从2遍历到kk】时 的最大/最小乘积
//当下次需要确定 选择k+1个人的最大乘积 时,只需要 先确定第k+1个人的所有可能位置(也用one表示,one从k+1遍历到n),
// 然后 在确定好第k-2个人的所有可能位置后,通过计算 这些位置的最大/最小乘积 与 arr[one]的乘积,确定选择k+1个人,位置为one时的最大/最小乘积
}
}
/*
* kk=3 dd=2
* arr={0, -10, -20, -30, -40};
* f[1][1]=-10、f[2][1]=-20、f[3][1]=-30、f[4][1]=-40
* g[1][1]=-10、g[2][1]=-20、g[3][1]=-30、g[4][1]=-40
*
* k=2:
* one: 2~4
* one:2
* f[2][2]=Long.MIN_VALUE
* g[2][2]=Long.MAX_VALUE
* left: 1~1
* left:1
* f[2][2]=max(Long.MIN_VALUE,max(f[1][1]*arr[2],g[1][1]*arr[2]))=max(Long.MIN_VALUE,max(-10*-20,-10*-20))=max(Long.MIN_VALUE,200)=200
* g[2][2]=min(Long.MAX_VALUE,min(g[1][1]*arr[2],g[1][1]*arr[2]))=min(Long.MAX_VALUE,min(-10*-20,-10*-20))=min(Long.MIN_VALUE,200)=200
* one:3
* f[3][2]=Long.MIN_VALUE
* g[3][2]=Long.MAX_VALUE
* left:1~2
* left:1
* f[3][2]=max(Long.MIN_VALUE,max(f[1][1]*arr[3],g[1][1]*arr[3]))=max(Long.MIN_VALUE,max(-10*-30,-10*-30))=300
* g[3][2]=min(Long.MAX_VALUE,min(f[1][1]*arr[3],g[1][1]*arr[3]))=min(Long.MAX_VALUE,min(-10*-30,-10*-30))=300
* left:2
* f[3][2]=max(f[3][2],max(f[2][1]*arr[3],g[2][1]*arr[3]))=max(300,max(-20*-30,-20*-30))=600
* g[3][2]=min(g[3][2],min(f[2][1]*arr[3],g[2][1]*arr[3]))=min(300,min(-20*-30,-20*-30))=600
* one:4
* f[4][2]=Long.MIN_VALUE
* g[4][2]=Long.MAX_VALUE
* left: 2~3
* left:2
* f[4][2]=max(Long.MIN_VALUE,max(f[2][1]*arr[4],g[2][1]*arr[4]))=max(Long.MIN_VALUE,max(-20*-40,-20*-40))=800
* g[4][2]=min(Long.MAX_VALUE,min(f[2][1]*arr[4],g[2][1]*arr[4]))=min(Long.MIN_VALUE,min(-20*-40,-20*-40))=800
* left:3
* f[4][2]=max(f[4][2],max(f[3][1]*arr[4],g[3][1]*arr[4]))=max(800,max(-30*-40,-30*-40))=1200
* g[4][2]=min(g[4][2],min(f[3][1]*arr[4],g[3][1]*arr[4]))=min(800,min(-30*-40,-30*-40))=1200
* one:5
* f[5][2]=Long.MIN_VALUE
* g[5][2]=Long.MAX_VALUE
* left: 3~4
* left:3
* f[5][2]=
* g[5][2]=
* left:4
* f[5][2]=
* g[5][2]=
* k=3:
* one:3~4
* one:3
* f[3][3]=Long.MIN_VALUE
* g[3][3]=Long.MAX_VALUE
* left:2~2
* f[3][3]=max(Long.MIN_VALUE,max(f[2][2]*arr[3],g[2][2]*arr[3]))=max(Long.MIN_VALUE,max(200*-30,200*-30))=-6000
* g[3][3]=min(Long.MAX_VALUE,min(f[2][2]*arr[3],g[2][2]*arr[3]))=min(Long.MAX_VALUE,min(200*-30,200*-30))=-6000
* one:4
* f[4][3]=Long.MIN_VALUE
* g[4][3]=Long.MAX_VALUE
* left:2~3
* left:2
* f[4][3]=max(Long.MIN_VALUE,max(f[2][2]*arr[4],g[2][2]*arr[4]))=max(Long.MIN_VALUE,max(200*-40,200*-40))=-8000
* f[4][3]=min(Long.MAX_VALUE,min(f[2][2]*arr[4],g[2][2]*arr[4]))=min(Long.MAX_VALUE,min(200*-40,200*-40))=-8000
* left:3
* f[4][3]=max(f[4][3],max(f[3][2]*arr[4],g[3][2]*arr[4]))=max(f[4][3],max(600*-40,600*-40))=-24000
* g[4][3]=min(g[4][3],min(f[3][2]*arr[4],g[3][2]*arr[4]))=min(f[4][3],min(600*-40,600*-40))=-24000
*
* */
//当计算出来第k个人所有可能位置的最大乘积后,只需要从这些所有可能位置中挑选出最大的那个最大乘积即可
long result = Long.MIN_VALUE;
for (int one = kk; one <= n; one++) {
if (result < f[one][kk]) {
result = f[one][kk];
}
}
System.out.println(result);
}
}