合唱团


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




网址:​​http://www.nowcoder.com/questionTerminal/661c49118ca241909add3a11c96408c8​

……想了半天,居然想不出来如何解释状态转移方程是怎么想出来的。这只能说,是套路。

这个思考过程待填,下面直接说状态的定义。

f [ i ] [ j ] [ 最大 / 最小 ]

分别表示,以第i个人为最后一个(也是必选的)人,加上这个人,已经选了 j 个人,最大可能的乘积和最小可能的乘积。

——为什么不是只记录最大的,还要记录最小的?

——因为最小的,很可能是一个负数,有着极大的绝对值,再乘一个负数,就变成最大的正数,也就是最优解了。

然后考虑,这个状态由哪些状态转移过来?

j 人,明显是从j-1个人的状态,最后加1个人(当前考虑的 i )而来。

第 i 人,根据题目要求,编号差不能大于d。那我们就往前观察最多d个人,从i-d到i-1,选了j-1个人中,选择和自己相乘,最大/最小的。

注意考虑边界条件:只选了一个人,就是 i 自己。

最后,解很大,请使用long long(C++)/ long (Java、C#)来保存中间计算结果。


​​

1. #include <stdio.h>  
2. #include <ctype.h>
3. #include <string.h>
4. #include <stdlib.h>
5. #include <limits.h>
6. #include <math.h>
7. #include <algorithm>
8. using namespace std;
9. typedef long long ll;
10.
11. int a[55];
12. ll f[55][15][2];
13.
14. int main(){
15. int n,kk,d;
16. "%d",&n);
17. for(int i=1;i<=n;++i){
18. "%d",&a[i]);
19. }
20. "%d%d",&kk,&d);
21. ll ans=0;
22. for(int i=1;i<=n;i++){
23. f[i][1][0]=f[i][1][1]=a[i];
24. for(int j=2;j<=kk;++j){
25. for(int k=i-1;k>=max(i-d,1);--k){
26. f[i][j][0]=max(f[i][j][0],max(f[k][j-1][0]*a[i],f[k][j-1][1]*a[i]));
27. f[i][j][1]=min(f[i][j][1],min(f[k][j-1][0]*a[i],f[k][j-1][1]*a[i]));
28. }
29. }
30. ans=max(ans,max(f[i][kk][0],f[i][kk][1]));
31. }
32. "%lld\n",ans);
33. return 0;
34. }