合唱团_牛客网【动态规划实现】

有 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);
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值