动态规划题解 D002 合唱团

题目解读

原题链接: 牛客网在线编程题 2017年校招专题

题目描述

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

输入描述

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

输出描述

输出一行表示最大的乘积

示例一

输入
3
7 4 7
2 50

输出
49

题意理解

题目的意思还是较为清晰的,即在一定的约束条件下寻找一个子序列,使得子序列的值乘积最大。这里的约束条件有两个:一个是子序列的个数是固定的为k;另一个是子序列相邻两个序号之间的差值有一个上限d。

算法分析

拿到这样一个题目一开始不太好下手,比如我现在取序列中的一个值出来,这个值位于子序列中的哪个部分呢?子序列中的下一个值应该是向前搜索还是向后搜索呢?这些都是比较困惑人的。但是仔细一想,并回忆动态规划问题中常见的思路:我们可以人为地确定当前搜索到的这个编号为子序列中的最后一个位置。这样的话,我们可以设置一个DPB[index][k],这个数组的含义即表示,以编号index的学生作为数量为k的子序列的最后一项对应的子序列最大乘积;
同样地,可以设置DPS[index][k]表示对应子序列的最小乘积。
通过设计这样的数据结构,我们其实将诸多变化量中的一些变量进行了固定,缩小了搜索的范围。
在设计出子状态后,我们期望得到状态转移方程。在这里,考虑到第二个约束条件,我们能够搜索的第k-1个子序列对象应当对应于原序列中编号为 [ index-d , index-1 ] 这样区间内的。由于对于index编号的同学的价值可正可负,因此我们需要将它与前一个状态最大或者最小的乘积同时相乘进行比较,才能得出当前编号条件下的最大和最小;由此,可以得到状态转移方程为:
DPB[index][k] = max(DPB[i][k-1]*value[index] , DPS[i][k-1]*value[index] ) 其中i的取值区间为 [ index-d , index-1 ]
DPS[index][k] = min(DPB[i][k-1]*value[index],DPS[i][k-1]*value[index] ) 其中i的取值区间为 [ index-d , index-1 ]

Part 1 初值的确定
由上面的分析可以知道,对应于DPB[index][1] = value[index] ; DPS[index][1] = value[index] ;

Part 2 状态转移方程的编写
第一重循环用来确定子序列的长度,即从2循环到k;
第二重循环用来确定哪些编号可以进行子序列的搜索,这是因为比如我现在将编号1作为一个个数为3的子序列的最后一项,这是显然不合理的,因为它的长度根本不满足条件。所以这个编号的范围应该介于第一重循环的值到n
第三重循环用来确定能够向前搜索的长度,注意应该是max(index-d,1)作为初值的选择

Part 3 最终结果的选取
我们应该从DPB[k][k]开始遍历到DPB[n][k]为止,选取其中最大的值作为结果

两个注意事项

事项1: 在计算的过程中,由于涉及int的乘法,故有可能出现溢出的情况,应该使用long作为变量类型
事项2: 在确定max(index-d,1)这个第三重循环时,注意在循环过程中可能会出现DBP[a][b]中a

代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#define MAXNUM 51

using namespace std ;

int n,k,d;
long value[MAXNUM];
long DPB[MAXNUM][MAXNUM];
long DPS[MAXNUM][MAXNUM];

long max(long a,long b)
{
    return (a>b?a:b);
}

long min(long a,long b)
{
    return (a<b?a:b);
}

void readData()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%ld",&value[i]);
    }
    scanf("%d%d",&k,&d);
    return ;
}

void init()
{
    for(int i=0;i<MAXNUM;i++){
        for(int j=0;j<MAXNUM;j++){
            DPS[i][j]=99999;
            DPB[i][j]=-99999;
        }
    }
    for(int i=1;i<=n;i++){
        DPB[i][1] = value[i];
        DPS[i][1] = value[i];
    }
    return ;
}

void Search()
{
    long max_num = -9999;
    //第一层循环用来挑选k个人
    for(int i=2;i<=k;i++){
        //第二层循环用来表示第几个人对应于i编号
        for(int j=i;j<=n;j++){
            //第三层循环用来表示可以搜索的前向范围
            for(int c=max(j-d,1);c<j;c++){
                if(c<i-1){
                    continue;
                }
                DPB[j][i]=max(DPB[j][i],max(DPB[c][i-1]*value[j],DPS[c][i-1]*value[j]));
                DPS[j][i]=min(DPS[j][i],min(DPS[c][i-1]*value[j],DPB[c][i-1]*value[j]));
            }
        }
    }
    for(int i=k;i<=n;i++){
        if(max_num<DPB[i][k]){
            max_num = DPB[i][k];
        }
    }
    printf("%ld\n",max_num);
    return ;
}

int main()
{
    readData();
    init();
    Search();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ComeTender/article/details/81416456