网易校招真题——合唱团

牛客网编程题——合唱团

题目描述

有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?

输入描述:

每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

输出描述:

输出一行表示最大的乘积。


思路

对于本题,先不考虑学生能力值为负的情况,可以把状态设置为“选择的学生数为i,最后一个选择的学生编号为j”的情况下的最大乘积,把状态存在一个数组里,这个状态就可以记为dp[i][j]。我们可以发现dp[i][j]的值只跟数组dp[i-1]中的各个项有关——在最后一个人编号是j的情况下,倒数第二个人的编号只能是j-d到j-1。因此,求dp[i-1][j-d] ~ dp[i-1][j-1]的最大值,再乘以a[j],就能够得到dp[i][j]。其中,dp[i-1]的各个项是由之前递推而来的(从0开始向上递推),于是我们可以直接使用结果而不必进行重复计算。

现在需要考虑学生能力值为负了,由于负负得正,所以不仅需要保存当前状态下的最大值,还要保存最小值,这样碰到负值,拿最小值乘才能得到真正的最大值。所以dp数组应该改为两个数组dmax,dmin。

最后一个问题是边界问题,递推总是要有边界的。由于是求乘积,所以在边界状态“选0个数”的情况下,不管最后一个选择的编号是几,数组都应当存1。还有,对于任意的i(选取学生的个数),显然j(最后一个学生的编号)必须至少为i,否则是不合常理的。

最后求数组dp[k]的项的最大值即可(dmax[k][i]代表“选取k个学生,最后一个学生是i”,应从dp[k][k]枚举)。下面给出程序代码,需要注意的是,这里学生的编号是1~n,与暴力解法的代码是不同的(学生编号0~n-1)。

代码

#include
#include

using namespace std;

int n,k,d,a[50];
long long dmin[11][51],dmax[11][51];

int main(){
    fill(dmin[0],dmin[1],1);
    fill(dmax[0],dmax[1],1);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    cin>>k>>d;
    for(int i=1;i<=k;i++){
        for(int j=i;j<=n;j++){
            int s=max(i-1,j-d);
            long long cmin=dmin[i-1][s],cmax=dmax[i-1][s];
            for(;s1][s]);
                cmax=max(cmax,dmax[i-1][s]);
            }
            if(a[j]<0){
                dmin[i][j]=cmax*a[j];
                dmax[i][j]=cmin*a[j];
            }
            else{
                dmax[i][j]=cmax*a[j];
                dmin[i][j]=cmin*a[j];
            }
        }
    }
    long long ans = dmax[k][k];
    for (int i = k + 1; i <= n; ++i)
        ans = max(ans, dmax[k][i]);
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(C++,校招真题)