记录一下:从此题开始正式慢慢研究一下动态规划。源代码来自牛客网;
题目:有 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
---------------------------------------------------------------------------------------------------------------------------------------------------------------
首先谈下动态规划吧,这个是算法里面一直比较难的,当我拿到这个题的时候,有点难以下手,虽然知道要用动态规划但是如何用,自己完全不知道,首先想到找出这个n个数中k个最大的相乘 ,但是很遗憾不对,①要求相邻两个学生之间的编号差不能超过d,②能力值存在负数。
动态规划是要将问题分解若干子问题,同时子问题之间可能存在包含,我们通常需要保存子问题的结果。
怎么保存了?数组f[n][m]表示选了n个人方案,最后一的位置为m,那f[n-1][p]就表示选了n-1个人,最后一个位置为p
怎么分解呢?首先m个人中选n个编号差要求<=d,若已经知道选n-1人的方案,同时这个方案最后一个人的位置为p,我们只需要遍历p+1到p+d的位置求得第n个人的位置。选n个人实际上不知道哪一个作为结尾所以会遍历的求f[n][1]~f[n][m]的最大值
有负数怎么办?因为有负数所以我们在加一个数组fmin[n][m]表示m个人中选n个间隔为d最小的乘积,最小的也可能成为最大的。
f[n][m]=max(f[n][m],max(f[n-1][m-c])*arr[m],fmin[n-1][m-c]*arr[m])
arr[]表示能力值数组,c为间隔,这个表达式要从1循环到d进行计算,当n==1,f[n][m]=fmin[n][m]=arr[m];这个时候肯定会有疑问,为什么m个人当中选1个人就取arr[m],这不符合要求!其实没有关系,m是从1开始取的,也就是说
f[1][1]=arr[1],f[1][2]......f[1][50]=arr[50]
最后通过循环会一直取max,所以也不存在上面问题.
假设 arr[]={10,21,30,41,45,-2,-7,13},设d为5,我们在计算f[2][6]的时候,k=5
for(int c=1;c<=d;c++)
{
f[2][6]=max(f[2][6],max(f[1][6-c])*arr[6],fmin[1][6-c]*arr[6]);
fmin[2][6]=min(fmin[2][6],min(fmax[1][6-c]*arr[6],fmin[1][6-c]*arr[6]));
}
这样就能求出f[2][6],fmin[2][6],一直到最后求出f[k][n]; 最后还需要注意用int会溢出需要用 long long int
根据表格就是:要求f[2][6],就是求 f[1][5]、f[1][4]、f[1][3]、f[1][2]、f[1][1]、f[1][0]这5个数和arr[6] 乘积最大值,再和f[2][6]比较取最大
f[n][m] | m | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
n | |||||||||
1 | 10 | 21 | 30 | 41 | 45 | -2 | 13 | -7 | |
2 | 0 | 210 | 630 | 1230 | 1845 | 0 | 585 | 14 | |
3 | 0 | 0 | 6300 | 25830 | 55350 | 0 | 23985 | 630 | |
4 | 0 | 0 | 0 | 258300 | 1162350 | 0 | 719550 | 25830 | |
5 | 0 | 0 | 0 | 0 | 11623500 | 0 | 15110550 | 774900 | |
6 | |||||||||
7 | |||||||||
8 |
从结果可以看出最终的答案为f[5][7],也就是说选5个人的方案结尾为7的情况最优,建议不懂的时候认真看看这个表格
#include<iostream>
using namespace std;
/*记第k个人的位置为one,则可以用f[one][k]表示从n个人中选择k个的方案。然后,它的子问题,
需要从one前面的left个人里面,选择k-1个,这里left表示k-1个人中最后一个(即第k-1个)人的位置,因此,子问题可以表示成f[left][k-1].*/
int main()
{
int n,k,d;
long long int res;
res=0;
cin>>n;
int *arr=new int[n];
for(int i=0;i<n;i++)
{
cin>>arr[i];
}
cin>>k; //选取的 k名学生
cin>>d; //相隔编号不超过 d
long long int fmax[11][50]={0};
long long int fmin[11][50]={0};
for(int j=1;j<=k;j++)
{
for(int i=0;i<n;i++)
{
if(j==1)
{
fmax[j][i]=arr[i];
fmin[j][i]=arr[i];
//continue;
}
for(int p=1;p<=d;p++)
{
if(i-p>=0 && i-p<n)
{
fmax[j][i]=max(fmax[j][i],max(fmax[j-1][i-p]*arr[i],fmin[j-1][i-p]*arr[i])); // 在i个数中选取j个数 最大的成绩
fmin[j][i]=min(fmin[j][i],min(fmax[j-1][i-p]*arr[i],fmin[j-1][i-p]*arr[i]));
}
}
res=max(res,fmax[k][i]);
}
}
cout<<res;
if(arr !=NULL)
{
delete []arr;
arr=NULL;
}
return 0;
}