算法作业-动态规划
第一题
目标函数为 max g1(x1)+g2(x2)+g3(x3),其中x1、x2、x3的约束为它们均为非负整数,且它们平方的和小于等于10,所以该问题可以看作0-1背包问题,背包的最大容量是10,求目标函数的max值即求背包的最大价值。
设两个数组,F[k][y]和g[i][j],其中F[k][y]表示在平方的和的限制为y且取前k个x时的最大值,g[i][j]表示函数gi(xi)的值,从表中可以看出g(x)函数值与选择xk以及xk的值有关。
函数的递推式为:
F[k][y] = max F[k-1][y-xk*xk] + g[k,xk]
当平方的和约束为y且选择前k个x时,其最大值为在平方和约束为 y - xk * xk 且选择前k-1个x时的最大值再加上gk(xk)。选择前k个x,那么选择的xk的值也在平方的和y的限制中,它的前一个状态就是选择k-1个x,且平方的和限制为 y - xk * xk,也即减去了第k个x,前一个状态的最大值加上第k个x自己的值,选择二者之和的最大值作为现在状态的值,得到结果。
for(int k=1; k<=3; k++)
{
for(int y=0; y<=10; y++)
{
int max=0;
//x的平方不能大于y,否则下边y-pow(x,2)会越界
for(int x=0; x<=sqrt(y); x++)
{
if(F[k-1][y-pow(x,2)]+g[k,x]>max)
{
max=F[k-1][y-pow(x,2)]+g[k,x];
//标记限制为y且选择前k个x时第k个x的值
Flag[k,y]=x;
}
}
}
}
//输出
//最优值
cout<<F[3][10]<<endl;
int i=3,y=10;
while(i)
{
cout<<"x"<<i<<"="<<Flag[i][y]<<endl;
y-=Flag[i][y]*Flag[i][y];
i--
}
/*
结果
x1=1,x2=2,x3=2,最大值37
*/
最后输出就是最终条件下选择的第三个x的值,然后y减去这个x的平方,i-1,F[i-1][y-pow(x,2)]就是选择的第二个x的值,第一个x的值也是如此。
第二题
首先,这个题是给定了n个整数组成的序列且分成m段,已经指出了要分成多少段。
用动态规划求解,声明dp[i][j]数组,表示i个整数分成j段的子序列和的最大值的最小值,所谓子序列和的最大值的最小值,因为将i个数分成j段,有很多种分法,对于每一种分法,这j个子序列和都有一个最大值,所以每一种分法都有一个最大值,最优的分段法就是比较每一种分法的子序列和的最大值,其中的最小值的那种分法就是最优分段法。
对于dp[i][j]数组,首先要进行初始化
for(int i=1; i<=n; i++)
{
dp[i][1]=dp[i-1][1]+a[i];
}
这里数组a[i]保存了输入的整数,dp[i][1]就是把i个数分成一份,也即这i个数的和,因此 dp[i][1]就等于dp[i-1][1]再加上a[i],也即前i-1个数的和加上第i个数。
递推公式:
tmp = max dp[k][j-1], dp[i][1]-dp[k][1]
dp[i][j] = min tmp
递推公式分为两部分,一是选取子序列和的最大值,二是选择这些最大值中的最小值。
选取子序列和的最大值时比较了最后一段子序列和前面j-1段子序列和的最大值,这里k为前面j-1段子序列的最后一位的下标,dp[i][j]表示i个数分成j段,这里把最后一段和前面分开,dp[k][j-1]就是前面k个数分成j-1段的情况下子序列和最大值的最小值,dp[i][1]-dp[k][1]就是整个序列的和减去前k个数的和,就等于最后一段子序列的和,选择二者之中的最大值就得到了分成j段时子序列和的最大值,最后dp[i][j]等于最大值中的最小值。
for(int i=1; i<=n; i++)
{
//n个数,除去分一段,最少分2段,最多分n段
//这里有限制,最多只能是m段
for(j=2; j<=m; j++)
{
//INF=0x3f3f3f3f
min=INF;
//一共是i个数
for(int k=1; k<=i; k++)
{
tmp=max(dp[k][j-1],dp[i][1]-dp[k][1]);
if(tmp<min)
{
min=tmp;
}
}
dp[i][j]=min;
}
}
//输出
cout<<dp[n][m]<<endl;