首页 > 试题广场 >

合唱团

[编程题]合唱团
  • 热度指数:104990 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
有 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

学习了各位大佬的代码,使用两个一维矩阵的java代码

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] val = new int[n];
        for (int i = 0; i < n; i ++) {
            val[i] = sc.nextInt();
        }
        int k = sc.nextInt();
        int d = sc.nextInt();
        System.out.println(helper(val, n, k, d));
    }

    public static long helper(int[] val, int n, int k, int d){
        long maxdp[] = new long[n + 1];
        long mindp[] = new long[n + 1];
        Arrays.fill(maxdp, 1);
        Arrays.fill(mindp, 1);
        // 遍历待选取的第k~第1名学生
        for (int i = k - 1; i >= 0; i --) {
            // 第i名学生(1...i0...i1...n,i0<=j<=i1),j是第i名学生的下标,要求i的前面至少有i - 1个人
            // 则j>=i, 要求i的后面至少有k-i个人,则n-j>=k-i,j<=n-k+i
            for (int j = i; j <= n - k + i; j ++) {
                // 前一名学生(i+1)可以是当前学生下标往后延顺d个,m=[j+1, j+d]
                for (int m = j + 1; m <= j + d && m <=n; m ++) {
                    long temp1 = val[j] * maxdp[m];
                    long temp2 = val[j] * mindp[m];
                    maxdp[j] = Math.max(maxdp[j], Math.max(temp1, temp2));
                    mindp[j] = Math.min(mindp[j], Math.min(temp1, temp2));
                }
            }
        }
        long res = Long.MIN_VALUE;
        for (long x: maxdp) {
            res = Math.max(x, res);
        }
        return res;
    }
}
发表于 2019-08-29 15:04:57 回复(0)
对于这道题我的具体思路:
  1. 看到这道题,首先最基本的想法一般是从n中抽取k个数,然后验证是否符合条件,复杂度大概是是一个非指数级可解问题
  2. 接着进一步优化,首先可以发现因为限制了相对位置d,所以问题是位置相关的,而根据乘法交换率,计算时位置无关,因此保持其位置而不是考虑排序方法更好归纳问题。因此设人的序列为ListA:a1,a2,....,an
  3. 这道题主要超变量有ListA,N.K,D从这道题跟具体的人选取与不选取有关,从头或从尾砍掉或选取一部分人,可以保持这个问题本身性质不变而只在超变量有改变,具有子问题的性质,且是最优化问题且包含最优子问题,因此考虑上贪心算法或者动态规划算法,并且先不考虑正负问题。
  4. 贪心的2个要求是最优子问题和贪心选择(在子问题迭代过程每一步都是最佳,其中不会有其它选择优于我的选择【这也是很多贪心算法的证明】)考虑ListA中对an这个人的选择入k和抛弃,前面子问题会变成k-1或者k,显而易见直接丢掉算子问题不能保证最优,因此考虑动态规划。
  5. 动态规划要求是最优子问题,重叠子问题,而动态规划又经常被大牛叫为打表算法,因此考虑打表,动态规划首先都要列出递推方程,考虑以砍尾部来划归子问题
  6. (图里公式max的第一行d应该为大写D,其应该被重置)
  7. 但是这样就会被列为三维矩阵,非常麻烦且不直观,考虑到距离d会因为选择了某人而重置为D所以考虑另外一种切分方法
  8. 但是这种方法有个问题就是listA前面的后面可能会空一大截,不一定从尾巴或头部开始切,这也符合实际问题需要。于是表大概就长下面这个样子:
  9. 这样就可以解决最大问题了,但是因为涉及正负数,负数最小的可能在下一次乘法中反成最大,所以得考虑打两个表,一个最小表一个最大表,判断哪个大取哪个。
  10. 照这个思路的具体实现跟高赞答案一样
  1. import java.util.Scanner; //主要参考@菜鸡华的代码 //kk dd one命名引起不适所以替换成简洁有力的命名K D i //在N的遍历中使其终止与N-K+k,不进行剩下不必要的训练。 public class Main{     public static void main(String[] args){         Scanner sc = new Scanner(System.in);         while(sc.hasNext()) {             //读数据             int N = sc.nextInt();             int[] arr = new int[N + 1];             for (int i = 1; i <= N; i++) {                 arr[i] = sc.nextInt();             }             int K = sc.nextInt();             int D = sc.nextInt();                          //规划数组表示,使用两个数组保证,最大正数最小负数都有保存             long[][] f = new long[N + 1][K + 1];//人直接对应坐标,n和K都要+1             long[][] g = new long[N + 1][K + 1];             //初始化k=1的情况             for(int i = 1;i<=N;i++){                 f[i][1] = arr[i];                 g[i][1] = arr[i];             }             //自底向上递推             for(int k=2;k<=K;k++){                 for(int i = k;i<=N-K+k;i++){                     //求解当i和k定的时候,每步最优子问题                        long tempmax = Long.MIN_VALUE;                     long tempmin = Long.MAX_VALUE;                     for(int left = Math.max(k-1,i-D);left<=i-1;left++){                         if(tempmax<Math.max(f[left][k-1]*arr[i],g[left][k-1]*arr[i])){                             tempmax=Math.max(f[left][k-1]*arr[i],g[left][k-1]*arr[i]);                         }                         if(tempmin>Math.min(f[left][k-1]*arr[i],g[left][k-1]*arr[i])){                             tempmin=Math.min(f[left][k-1]*arr[i],g[left][k-1]*arr[i]);                         }                     }                     f[i][k] = tempmax;                     g[i][k] = tempmin;                 }             }             //从最后一行选取最终最大值             long result = Long.MIN_VALUE;             for(int i = K;i<=N;i++){                 if(result<f[i][K]){                     result = f[i][K];                 }             }             System.out.println(result);         }     } }

发表于 2018-12-25 16:55:19 回复(1)

import java.util.Scanner;
 
public class Main {
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNextInt()) {
            int n = sc.nextInt();          
            int[] array = new int [n+1];
            for (int i = 1; i <=n ; i++) {
                array[i] = sc.nextInt();
            }
            int K = sc.nextInt();           
            int d = sc.nextInt();            
            long[][] fmax = new long[K+1][n+1]; 
            long[][] fmin = new long[K+1][n+1]; 
            long  res = Integer.MIN_VALUE; 
            for (int i = 1; i <=n; i++) {
                fmax[1][i] = array[i];
                fmin[1][i] = array[i];
                for (int k = 2; k <=K; k++) {
                    for (int j = i-1 ; j > 0 &&  i-j<=d ; j--) {
                        fmax[k][i] = Math.max(fmax[k][i], Math.max(fmax[k-1][j] * array[i], fmin[k-1][j] * array[i]));
                        fmin[k][i] = Math.min(fmin[k][i], Math.min(fmax[k-1][j] * array[i], fmin[k-1][j] * array[i]));
                    }
                }
                res = Math.max(res ,fmax[K][i]);
            }
            System.out.println(res);
        }
        sc.close();
    }
}

发表于 2018-09-26 17:34:29 回复(0)
代码加注释看着不会忘
中心点:记第k个人的位置为x,则可以用f(x)(k)表示从n个人中选择k个人的方案中,第x号人作为第k个人的最大价值

import java.util.Scanner;
public class Main {
 
 public long getMaxVal(int[] val,int pep,int d) {
  
  int len=val.length;
  
  long[][] fmax=new long[len][pep];
  long[][] fmin=new long[len][pep];
  
 //怎样保持f(x)(k)表示选第x号人的最大价值?
    //因为考虑到有负值,所以要维护两个数组,一个存最大值,一个存最小值,**的题目
    // 一、首先给出递归的初始值
    for (int x = 0; x < len; x++) {  //当只选一个人的时候,f数组中的价值,
     fmax[x][0]=val[x];       //递归的初始值,有了这些初始值作为基础才能依次往上递增
     fmin[x][0]=val[x];       //当只选一个人时,f(x)(1)当然等于val[x];
 }
  /*  二、详细的阐述:当来考虑选谁作为第二个人时,因为按顺序选人,所以只能从1号开始选择(懂的      都懂)
     1、那我们姑且选1号,那fmax(1)(2)=f(0)(1)*val(1),fmin(1)(2)同上
     2、那选择2号,f(2)(2)等于啥呢,当然是比较第一个是选0号还是选1号的价值与2号相乘的结果,取最大和          最小两个人的情况以此类推
     3、现在考虑选三个人的情况,从2号开始选择(懂的都懂),我们姑且选3号作为第三个人,那fmax(3)(3)=啥呢?
      fmax(2)(3)当然是等于第二个人选1号或者选2号的价值和val(3)相乘的结果决定。
     
      4、经过一层一层的递归,最终到选择第k个人。我们想要的结果就是得到fmax[left->(k-1)][k]这一组数值中的最大值
  */
    for (int k = 1; k < pep; k++) {
    for (int x = k; x < len; x++) {
    //tempmax、tempmin放在第三层循环外面,记录第k-1个人选在[x-d,x-1]区间时,f[x][k]的最大最小值
                long tempmax = Long.MIN_VALUE;
                long tempmin = Long.MAX_VALUE;
     for (int left = Math.max(0,x-d); left <= x-1; left++) {
                
                 if(tempmax<Math.max(fmax[left][k-1]*val[x],fmin[left][k-1]*val[x])) {
                  fmax[x][k]=Math.max(fmax[left][k-1]*val[x],fmin[left][k-1]*val[x]);
                  tempmax=fmax[x][k];
                 }
                
                 if(tempmin>Math.min(fmax[left][k-1]*val[x],fmin[left][k-1]*val[x])) {
                  fmin[x][k]=Math.min(fmax[left][k-1]*val[x],fmin[left][k-1]*val[x]);
                  tempmin=fmin[x][k];
                 }
    }
   }
  }
     long max=0;
     for (int i = pep-1; i < len; i++) {
   if(fmax[i][pep-1]>max) {
    max=fmax[i][pep-1];
   }
  }
  return max;
 }
   
   
     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();
           
            Main hq=new Main();
      long maxVal = hq.getMaxVal(arr, kk, dd);
         System.out.println(maxVal);
         break;
  }
  
  
 }
   
   
   
   
   
}
发表于 2018-08-15 18:23:28 回复(0)
package challenge;

import java.util.Scanner;

/**
 * @author Eden
 * @version 创建时间:2018年7月21日 下午6:25:29
 * @deprecated:
 *  题目描述
        有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学
        生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
    输入描述:
        每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来
        的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,
        k 和 d (1 <= k <= 10, 1 <= d <= 50)。
    输出描述:
        输出一行表示最大的乘积。
 */
public class Chorus {
    public static int[] arr;//学生能力数组
    public static long[][][] resultArr;//resultArr[学生个数][已选择学生个数][最大/最小]暂存在第count次选择时它的最大与最小值
    public static int k;
    public static int d;
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        arr = new int[n];
        for(int i=0;i<n;i++){
            arr[i]=input.nextInt();
        }
        k = input.nextInt();//选取学生个数
        d = input.nextInt();//编号相隔最大距离
        resultArr = new long[n][k+1][2];
        long[] result;
        long max=0;
        for(int i=0;i<n-k+1;i++){
            result = calculate(i,1);
            if(i==0)
                max=result[0];
            else
                if(result[0]>max)
                    max=result[0];
        }
        System.out.println(max);
    }
    
    public static long[] calculate(int index,int count){
        long[] result = {1,1};
        if(count>k){
            return result;
        }
        long max=arr[index],min=arr[index];
        //选择编号相隔有限制,rear表示已当前学生index为基准,能选择后面最大学生编号
        int rear = index+d+1;
        long firstTempResult,secondTempResult;
        if(rear>arr.length){
            rear=arr.length;
        }
        //递归求解
        for(int i=index+1;i<rear;i++){
            if(resultArr[i][count][0]==0&&resultArr[i][count][1]==0){
                firstTempResult = calculate(i,count+1)[0]*arr[index];
                secondTempResult = calculate(i,count+1)[1]*arr[index];
            }else{
                firstTempResult = resultArr[i][count][0]*arr[index];;
                secondTempResult = resultArr[i][count][1]*arr[index];
            }
            if(firstTempResult>max)
                max=firstTempResult;
            if(firstTempResult<min)
                min=firstTempResult;
            if(secondTempResult>max)
                max=secondTempResult;
            if(secondTempResult<min)
                min=secondTempResult;
        }
        result[0]=max;
        resultArr[index][count-1][0]=max;
        result[1]=min;
        resultArr[index][count-1][1]=min;
        //返回最大最小值
        return result;
    }
}

发表于 2018-07-24 14:29:59 回复(0)
 public static void main(String[] args) {
    Scanner scanner  = new Scanner(System.in);
    String line = scanner.nextLine();
    int n = Integer.parseInt(line);

    line = scanner.nextLine();
    String[] s = line.split(" ");
    int arr[] = new int[n];
    for (int i = 0;i < n;i ++) {
        arr[i] = Integer.parseInt(s[i]);
    }

    line = scanner.nextLine();
    String []s1 = line.split(" ");
    int k = Integer.parseInt(s1[0]);
    int d = Integer.parseInt(s1[1]);
    dp(arr, k , d);
} 

public static void dp(int []arr, int k, int d) {
    //dp[i][j]表示以i为最后一个人的下标并且j为已选人数
    //由于有正有负,所以两个数组一个存最大值一个存最小值
    long [][]dpn = new long[arr.length][k + 1];
    long [][]dpm = new long[arr.length][k + 1];
    //以某个元素为结尾时的最大值
    long max = Integer.MIN_VALUE;
    for (int i = 0;i < arr.length;i ++) {
        dpn[i][1] = arr[i];
        dpm[i][1] = arr[i];
        //因为一共要选k个人,所以这里再做一个循环
        for (int kk = 2; kk <= k; kk ++) {
            for (int j = i - 1; j >= 0 && i - j <= d;j -- ) {
                //如果是正值,选大的
                dpm[i][kk] = Math.max(dpm[i][kk], Math.max(dpm[j][kk - 1] * arr[i], dpn[j][kk - 1] * arr[i]));
                //如果是负值,选小的
                dpn[i][kk] = Math.min(dpn[i][kk], Math.min(dpm[j][kk - 1] * arr[i], dpn[j][kk - 1] * arr[i]));
            }
        }
        max = Math.max(dpm[i][k], max);
    }
    System.out.println(max);
}
编辑于 2018-05-18 21:52:32 回复(0)
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main{
    public static void main(String []args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] a = new int[n];
        List ls = new ArrayList();
        
        for(int i=0;i<n;i++) {
             a[i]=in.nextInt();
        }
        for(int i=0;i<n;i++) {
            for(int j=0;j<n-i-1;j++) {
                if(a[j]<a[j+1]) {
                    int temp =a[j];
                    a[j]= a[j+1];
                    a[j+1]= temp;
                }
                
            }
        }
//        for(int i=0;i<n;i++) {
//            System.out.print(a[i]+" ");
//        }
        int k =in.nextInt();
        int d =in.nextInt();
        for(int i=0;i<n-1;i++) {
            if(a[i]-a[i+1]<=d) {
                ls.add(a[i]);
            }
            
        }
//        System.out.println(ls);
        int b=1;
        for(int i=0;i<k;i++) {
            b =b*(int) ls.get(i);
            
        }
        System.out.print(b);
        
        
    }
}
都是对的啊
发表于 2018-05-13 13:36:49 回复(0)
这是我提交的代码,代码正确,就是时间复杂度有点大
您的代码已保存
运行超时:您的程序未能在规定时间内运行结束,请检查是否循环有错或算法复杂度过大。
case通过率为70.00%
----------------------------------------------------------------------------------------
import java.util.Scanner;
import javax.xml.soap.Detail;

/**
 *@author liujun
 *@date: 2018-5-2 Time:下午11:00:55
 *@author—Email:ljfirst@mail.ustc.edu.cn
 *@description:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
 *@version
1.0
 */
public class Main {

    //学生人数以及每个学生的能力值、入选学生能力值和编号
    static int student_num ;
    static long [] student_power ;
    
    //筛选最佳值、记录最佳入选能力值和最佳能力序列
    static long sum_best = 1;
    static long [] student_chosed_power ;
    static long [] student_chosed_num ;
    
    //筛选暂时值,记录入选能力值和暂时能力序列
    static long sum_temp = 1;
    static long [] student_temp_power ;
    static long [] student_temp_num ;
    
    //挑选k个学生和设置d个间距
    static int k ,d ;
    
    //动态规划开始,定义递归深度、和暂时值
    static int depth = 0;
    
    //递归传入递归深度和轮到的学生编号
    public void digui (int depth, int start){
        //边界条件判断
        if (depth > k) {
            if(sum_temp > sum_best && distance(student_temp_num)){
                sum_best = sum_temp;
                for (int e = 0; e < student_temp_power.length; e++) {
                    student_chosed_num[e] = student_temp_num[e];
                    student_chosed_power[e] = student_temp_power[e];
                }
            }
            return ;
        }
        
        for (int j = start; j < student_num; j++) {
            if(student_power[j] == 0){
                continue;
            }
            sum_temp *= student_power[j];
            student_temp_power[depth] = student_power[j];
            student_temp_num[depth] = j;
            
            digui(depth+1, j+1);
            
            sum_temp /= student_power[j];
            student_temp_power[depth]= 0;
            student_temp_num[depth] = 0;
        }
        
        return;
    }
    
    public boolean distance(long [] student_chose_num){
        for (int u = 0; u < student_chose_num.length -1; u++) {
            if(student_chose_num[u+1] - student_chose_num[u] > d){
                return false;
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        
        student_num = scan.nextInt();
        student_power = new long [student_num];
        
        //能力值赋值
        for (int i = 0; i < student_num; i++) {
            student_power[i] = scan.nextInt();
        }

        //获取k个学生和设置d个间距
        k = scan.nextInt();
        d = scan.nextInt();
        
        //记录入选学生能力值和对应序号
        student_temp_power = new long[k];
        student_temp_num = new long[k];
        student_chosed_power = new long[k];
        student_chosed_num = new long[k];
        
        k--;
        new Main().digui(0, 0);
        System.out.print(sum_best);   
    }
}

发表于 2018-05-06 15:08:38 回复(0)

这个问题只要理解了从递归到动态规划的演变过程之后,就不难做出来了。

首先我们来看看递归的解题思路:

(cur:当前对象的下标,pre:上一次选择的对象的下标, count:已选择的人数,temp:当前乘积:result[0]:最终乘积)

对于n个人无非就是能不能选,以及选与不选的问题!

判断能不能选:需满足 cur - pre <= d

选与不选:就是递归的两个分支了

筛选结果:当count == k时,将当前乘积temp 与 最终乘积result[0] 择优存入result[0]中

递归出口:当cur >= n || count >= k时,由于筛选结果是在进入递归时的第一步操作,换言之,就是对上一次递归结果,进行筛选,所以当count==k时,已经可以返回了。

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] ability = new int[n];
        for (int i = 0; i < n; i++) {
            ability[i] = sc.nextInt();
        }
        int k = sc.nextInt();
        int d = sc.nextInt();

        long maxProduct = getMaxProduct(ability, k, d);
        System.out.println(maxProduct);
        sc.close();
    }
private static long getMaxProduct2(int[] ability, int k, int d) {
    if (ability.length == 1) {
        return ability[0];
    }
    long[] result = { Long.MIN_VALUE };
    int count = 0;
    int pre = -1;
    int cur = 0;
    long temp = 1;
    process(ability, k, d, count, pre, cur, temp, result);
    return result[0];
}

/**
 * @param ability
 * @param k
 * @param d
 * @param count 表示已选人数
 * @param pre    表示上一次选择的人的下标
 * @param cur    表示当前的人的下标
 * @param temp  用于保存当前乘积
 * @param result 用于存储结果
 * @return
 */
private static void process(int[] ability, int k, int d, int count, int pre, int cur, long temp,
        long[] result) {
    if (count == k) {
        result[0] = Math.max(temp, result[0]);
    }
    if (cur >= ability.length || count >= k) {
        return;
    }
    //可选
    if (pre == -1 || cur - pre <= d) {
        //选
        process(ability, k, d, count + 1, cur, cur + 1, temp * ability[cur], result);
        //不选
        process(ability, k, d, count, pre, cur + 1, temp, result);
    }
}

接下来分析一下怎么由递归演变到动态规划:
由于乘积,我们很容易想到,需要保存最大值和最小值(因为最大值可以=负数min,也可以=正数max)
所以开辟两个空间maxProduct和minProduct,然后只需要将递归过程中使用到的变量保存起来维护就好了。
如:
cur:就是i
pre:也是i
count:是j
temp:就是两个数组的值了
解释一下maxProduct[i][j]的含义:(maxProduct[i][j]类似)

表示将第i个对象(输入数组中的对象)作为第j个对象(选入数组中的对象)时,所能得到的最大乘积
所以当i=0时,表示只有一个人,所以无论将其作为第几个人选入,最大(小)乘积都是他的能力值 即 maxProduct[i][j] = minProduct[i][j] = ability[0];
当j=0时,表示只选一个人,故无论选择谁,最大(小)乘积都是他的能力值 即 maxProduct[i][j] = minProduct[i][j] = ability[i]
 
当 i j 都不为0时,需分三类讨论:
    当ability[i] == 0 时, 此时乘积一定为0
    当ability[i] > 0时, 那么我们需要找到一个作为第j - 1个对象选入时,获得最大乘积和最小乘积的人(两个)
    当ability[i] < 0时,我同样需要找到一个作为第j - 1个对象选入时,获得最大乘积和最小乘积的人(两个),只是保存的结果不一样。(具体见代码)
 
那么,最终结果就是当j = k-1时,遍历得到的max


public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] ability = new int[n];
        for (int i = 0; i < n; i++) {
            ability[i] = sc.nextInt();
        }
        int k = sc.nextInt();
        int d = sc.nextInt();

        long maxProduct = getMaxProduct(ability, k, d);
        System.out.println(maxProduct);
        sc.close();
    }

    private static long getMaxProduct(int[] ability, int k, int d) {
        long[][] maxProduct = new long[ability.length][k];
        long[][] minProduct = new long[ability.length][k];
        init(maxProduct, ability);
        init(minProduct, ability);

        for (int i = 1; i < ability.length; i++) {
            for (int j = 1; j < k; j++) {
                int start = i - d;
                start = start < 0 ? 0 : start;
                int end = i - 1;

                long maxTemp = getMaxFromMaxProduct(maxProduct, start, end, j - 1);
                long minTemp = getMinFromMinProduct(minProduct, start, end, j - 1);

                if (ability[i] == 0) {
                    maxProduct[i][j] = 0;
                    minProduct[i][j] = 0;
                }
                else if (ability[i] > 0) {
                    maxProduct[i][j] = ability[i] * maxTemp;
                    minProduct[i][j] = ability[i] * minTemp;
                }
                else {
                    maxProduct[i][j] = ability[i] * minTemp;
                    minProduct[i][j] = ability[i] * maxTemp;
                }
            }
        }
        long res = Long.MIN_VALUE;
        for (int i = 0; i < maxProduct.length; i++) {
            res = Math.max(res, maxProduct[i][k - 1]);
        }
        return res;
    }

    private static long getMinFromMinProduct(long[][] minProduct, int start, int end, int j) {
        long res = Long.MAX_VALUE;
        for (int i = start; i <= end; i++) {
            res = Math.min(minProduct[i][j], res);
        }
        return res;
    }

    private static long getMaxFromMaxProduct(long[][] maxProduct, int start, int end, int j) {
        long res = Long.MIN_VALUE;
        for (int i = start; i <= end; i++) {
            res = Math.max(maxProduct[i][j], res);
        }
        return res;
    }

    private static void init(long[][] arr, int[] ability) {
        for (int i = 0; i < arr.length; i++) {
            arr[i][0] = ability[i];
        }
        for (int j = 0; j < arr[0].length; j++) {
            arr[0][j] = ability[0];
        }
    }
编辑于 2018-04-24 16:53:42 回复(0)
输入的数据有正有负, 增加了问题的难度. 我使用两个二维数组来记录动态规划的结果, 分别保存以i为结尾, 选取j个元素得到的乘积的最大值和最小值. 在更新这两个数组时考虑下一个输入值(题目中的能力值)的符号.
发表于 2018-04-19 15:38:24 回复(0)
这题可以从前递推,也可以从后递推,我选择的是从前递推的思路
import java.util.Scanner;


public class Test {
    public static void main(String[] args){
        Scanner in =  new Scanner(System.in);
        int n = in.nextInt();    //总人数
        int[] num = new int[n];
        for(int i=0;i<n;i++)
            num[i] = in.nextInt();
        int k = in.nextInt();    //人数
        int d = in.nextInt();    //步长
        long[] max = new long[n];    //记录正值
        long[] min = new long[n];    //记录负值
        long max1 = 0;//flag
        long min1 = 0;
        for(int x=0;x<n;x++){
            if(num[x]>=0){
                max[x] = num[x];
                min[x] = 0;
            }else{
                max[x] = 0;
                min[x] = num[x];
            }
        }
        for(int x=1;x<k;x++){
            for(int y=0;y<n-x;y++){    //第n次时,由于要n个数相乘,那么最后一个能操作数在n-k处
                max1 = 0;
                min1 = 0;
                for(int z=1;z<=d;z++){
                    if(y+z>=n-x+1)
                        break;
                    if(num[y] == 0){
                        break;
                    }
                    if(num[y]>0){    //当前能力为正,则需要乘以正数得到最大
                        if(num[y]*max[y+z]>max1)
                            max1 = num[y]*max[y+z];
                        if(num[y]*min[y+z]<min1)
                            min1 = num[y]*min[y+z];
                    }else{
                        if(num[y]*min[y+z]>max1)
                            max1 = num[y]*min[y+z];
                        if(num[y]*max[y+z]<min1)
                            min1 = num[y]*max[y+z];
                    }
                }
                max[y] = max1;
                min[y] = min1;
            }
        }
        max1 = max[0];
        for(int x=1;x<=n-k;x++){    //确定边界,最后也是在n-k处
            if(max[x] > max1)
                max1 = max[x];
        }
        System.out.println(max1);
    
    }
    
    
    
}


发表于 2018-04-11 10:17:18 回复(0)
import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner input=new Scanner(System.in);
        while(input.hasNext()){
            int n=input.nextInt();
            int[] arr=new int[n+1];        //arr[0]就不要了,看着方便,下面同理
            for(int i=1;i<n+1;i++)
                arr[i]=input.nextInt();
            int k=input.nextInt();
            int d=input.nextInt();
            long[][] dp_min=new long[n+1][k+1];    //当i为最后一个学生时(1<=i<=n),乘k次后的能力值数组
            long[][] dp_max=new long[n+1][k+1];
            for(int i=1;i<n+1;i++){            //选第一位学生
                dp_min[i][1]=arr[i];
                dp_max[i][1]=arr[i];
            }
            for(int i=2;i<k+1;i++){                        //选2-k位学生
                for(int last=i;last<n+1;last++){            //先定最后一位last,再定前k-1位
                    long temp_max=Long.MIN_VALUE;
                    long temp_min=Long.MAX_VALUE;
                    for(int left=Math.max(i-1,last-d);left<last;left++){        //left为i(循环)和last-d中的最大值(边界),遍历到最后一位,取min,max
                        if(temp_max<Math.max(dp_min[left][i-1]*arr[last],dp_max[left][i-1]*arr[last]))
                            temp_max=Math.max(dp_min[left][i-1]*arr[last],dp_max[left][i-1]*arr[last]);    //最大值乘负数,最小值乘正数
                        if(temp_min>Math.min(dp_min[left][i-1]*arr[last],dp_max[left][i-1]*arr[last]))
                            temp_min=Math.min(dp_min[left][i-1]*arr[last],dp_max[left][i-1]*arr[last]);
                    }
                    dp_max[last][i]=temp_max;
                    dp_min[last][i]=temp_min;
                }
            }
            long res=Long.MIN_VALUE;
            for(int i=1;i<n+1;i++){
                if(res<dp_max[i][k])
                    res=dp_max[i][k];
            }
            System.out.println(res);
        }
        
    }
}

发表于 2018-02-22 16:21:47 回复(0)
import java.util.Scanner;

public class Main {
    // 参考 【小刀初试】的算法思想, Java版本,便于理解
	public static void main(String[] args) {
		Scanner cin = new Scanner(System.in);
		while (cin.hasNextInt()) {
			int n = cin.nextInt(); 			// n 个学生
			int[] arr = new int [n+1];
			for (int i = 1; i <=n ; i++) {
				arr[i] = cin.nextInt();
			}
			int K = cin.nextInt();			  // 选出K个
			int d = cin.nextInt();			  // 两个学生的位置编号的差不超过 d
			long[][] fmax = new long[K+1][n+1]; // 记录最大乘积
			long[][] fmin = new long[K+1][n+1]; // 记录最小乘积
			// fmax[k][i]表示 : 当选中了k个学生,并且以第i个学生为结尾,所产生的最大乘积;
			// fmin[k][i]表示 : 当选中了k个学生,并且以第i个学生为结尾,所产生的最小乘积;
			//初始化第一行
			long  res = Integer.MIN_VALUE; // 记得用Long类型,考虑数值范围
			for (int i = 1; i <=n; i++) {
				fmax[1][i] = arr[i];
				fmin[1][i] = arr[i];
				for (int k = 2; k <=K; k++) {
					for (int j = i-1 ; j > 0 &&  i-j<=d ; j--) {
						fmax[k][i] = Math.max(fmax[k][i], Math.max(fmax[k-1][j] * arr[i], fmin[k-1][j] * arr[i]));
						fmin[k][i] = Math.min(fmin[k][i], Math.min(fmax[k-1][j] * arr[i], fmin[k-1][j] * arr[i]));
					}
				}
				res = Math.max(res ,fmax[K][i]);
			}
			System.out.println(res);
		}
	}
}


发表于 2016-09-03 20:32:30 回复(10)

问题信息

难度:
14条回答 71726浏览

热门推荐

通过挑战的用户

查看代码