动态规划——合唱团

题目:
有 n 个学生站成一排,每个学生有一个能力值,从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,返回最大的乘积。
每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。

思路:
一般的动态规划题目,中间使用的表的最后一个元素,dp[N][K]就是所求的结果。但这个题目不能这样,因为如果那样建表,子问题就成了“在前n个学生中,取k个,使乘积最大”——然而,本题目有额外的限制条件“相邻两个学生的位置编号的差不超过d”就没有办法代入递推公式了,因为子问题中本身并不包含位置信息。
因此将子问题定为:在前n个学生中,取k个,第n个必取,使乘积最大。这样的话,最终的结果就不是dp[N][K],而是dp[..][K]这一列中最大的那个值。
其次,求最大乘积比求最大和的问题要复杂许多。求最大和的话,子问题中也只需要求最大和就行了。但求最大乘积的时候,在子问题中,每一步需要求最大正积和最小负积。因为如果某学生的能力值为负数,乘以前面求得的最小负积,结果才是最大乘积。
再次,这个问题最后算的数据比较大,已经不是int型能够包含的了,需要用long long。

#include <stdio.h>
inline long long max(long long a,long long b){return (a>b?a:b);}
inline long long min(long long a,long long b){return (a>b?b:a);}
int main(){
    int N,K,D,i,j,k;
    //fm数组用来存储选了k个人并且以第i个同学结尾时候,所产生的最大乘积
    //fn数组用来存贮选了k个人并且以第i个同学为结尾时,所产生的最小乘积
    //N代表学生总数 
    long long stu[51],fm[11][51],fn[11][51],ans;
    while(~scanf("%d",&N)){
        for(i=0;i<N;scanf("%lld",&stu[++i]));       

        scanf("%d %d",&K,&D); //输入限制的人数K 和每次最多隔多少人D
        for(i=0;i<=K;++i)
            for(j=0;j<=N;fm[i][j]=fn[i][j]=0,++j); //这是根据输入K,来初始化fm和fn数组内容都为0
        for(i=1,ans=~0LL;i<=N;++i){
            fm[1][i]=fn[1][i]=stu[i]; //从第一位开始,fn,fm的内容必然一样
            for(k=2;k<=K;++k){ 
                for(j=i-1;j>0 && i-j<=D;--j){
                    //k=1在上面讨论过了,现在逐次递增k,并且记录要k个人,到第i个同学时候的最大值和最小值
                    fm[k][i]=max(fm[k][i],max(fm[k-1][j]*stu[i],fn[k-1][j]*stu[i]));
                    //从上一个fm[k-1][j] * stu[i]和fn[k-1][j]*stu[i]中选取一个最大值 填入当前的fm[k][i];
                    fn[k][i]=min(fn[k][i],min(fn[k-1][j]*stu[i],fm[k-1][j]*stu[i]));
                }
            }
            ans=max(ans,fm[K][i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36120793/article/details/79356383