题目链接:
http://lx.lanqiao.cn/problem.page?gpid=T136
问题描述
对于n个数,从中取出m个数,如何取使得这m个数的乘积最大呢?
输入格式
第一行一个数表示数据组数
每组输入数据共2行:
第1行给出总共的数字的个数n和要取的数的个数m,1<=n<=m<=15,
第2行依次给出这n个数,其中每个数字的范围满足:a[i]的绝对值小于等于4。
输出格式
每组数据输出1行,为最大的乘积。
样例输入
1
5 5
1 2 3 4 2
样例输出
48
一:首先要分离正负,两个负值乘积为正。做这道题时让我想起了按键盘http://blog.csdn.net/sm9sun/article/details/53241628
与其一样的是,按键盘保留大小写两种状态,那么本题也要保留正负最大值两种状态
二:类似于01背包,我们把m当作容量,每个数的体积都是1
这样一来每个数都面临取或者不取两种状态,当我们遍历到第i个物品时,我们要保留在i个物品中取j个物品的最大正负值即可
#include<stdio.h>#include<math.h>double fmax(double a,double b){ return a>b?a:b;}double fmin(double a,double b){ return a<b?a:b;}int main(){ double a[20];double fdp[20][20][20];double dp[20][20][20]; int m,n,i,j,k,t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m);for(i=1;i<=n;i++) scanf("%lf",&a[i]);for(i=0;i<=n;i++) for(j=0;j<=m;j++) for(k=0;k<=j;k++) { dp[i][j][k]=1; fdp[i][j][k]=1; } fdp[0][1][1]=dp[0][1][1]=a[1]; for(i=1;i<=n;i++) //遍历n个数{for(j=1;j<=m&&j<=i;j++) //m为容量,从i个数中取j个数{ for(k=1;k<=j;k++) //k遍历当我容量上限为j时取不同个数的最大值,这里与01背包不同,取值必须达到m容量,不能小于 { //所以每一个k都要记录下来if(a[i]>0){dp[i][j][k]=fmax(dp[i-1][j][k],dp[i-1][j-1][k-1]*a[i]);fdp[i][j][k]=fmin(fdp[i-1][j][k],fdp[i-1][j-1][k-1]*a[i]);}else{dp[i][j][k]=fmax(dp[i-1][j][k],fdp[i-1][j-1][k-1]*a[i]);fdp[i][j][k]=fmin(fdp[i-1][j][k],dp[i-1][j-1][k-1]*a[i]);} // printf("%.0lf,%.0lf ",dp[i][j][k],fdp[i][j][k]);}//printf("---");}//printf("\n");}printf("%.0lf\n",dp[n][m][m]);}return 0;}
用dp思想的确可以解决这道题,不过后来看了网上的答案,发现其实还有更简单的方法,用贪心~
我们将正负数分组排序后,依次选取两个最大正值乘积,两个最大负值乘积即可~
注意,如果m为奇数时要再乘一个正数。
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int main(){int t, n, m;int num[20];scanf("%d", &t);while(t--){scanf("%d %d", &n, &m);for(int i = 0; i < n; i++)scanf("%d", &num[i]);sort(num, num+n);int i, j, a, b;i = j = 0;long long ans = 1;while(m){a = num[i]*num[i+1];b = num[n-j-1]*num[n-j-2];if(a>=b && m>=2){ans*=a;i+=2;m-=2;}else{ans*=num[n-j-1];j++;m--;}}printf("%lld\n", ans);}return 0;}
所以说,也不能盲目的运用dp,有一些问题还是可以用贪心解决的。