本文利用markdown基于https://blog.csdn.net/qq_41926985/article/details/105627049重写,代码部分为本人编辑
代码要求
应用动态规划方法,求解投资问题,实现下面的例子。
#define MAX_N 4 //最大投资项目数目
#define MAX_M 5 //最大投资钱数(万元)
//f[i][j]的意义:第 i(从 1 开始)个项目投资 j 万元的收益
int f[MAX_N+1][MAX_M+1] = {
{0,0,0,0,0,0},
{0,11,12,13,14,15},
{0,0,5,10,15,20},
{0,2,10,30,32,40},
{0,20,21,22,23,24}
};
投资问题
什么是投资问题
有 m m m元钱, n n n项投资, f i ( x ) f_i(x) fi(x):将x元投入第i个项目的效益。求使得的总效益最大的投资方案。
举个例子:现在有两个项目x是钱数(单位:元),
f i ( x ) f_i(x) fi(x):将x元钱投资到第i个项目产生的效益
注意:使用的是总共的x钱数投资两个项目,而不是分别投资。
将0元投资这两个项目,则最大收益就是0
将1元投资这两个项目,不难看出
f 1 ( 1 ) + f 2 ( 0 ) = 11 f_1(1)+f_2(0)=11 f1(1)+f2(0)=11
,是最大收益
将2元投资这两个项目,不难看出
f 1 ( 2 ) + f 2 ( 0 ) = 12 f_1(2)+f_2(0)=12 f1(2)+f2(0)=12
,是最大收益
将3元投资这两个项目,
m a x ( f 1 ( 0 ) + f 2 ( 3 ) , f 1 ( 1 ) + f 2 ( 2 ) , f 1 ( 2 ) + f 2 ( 1 ) , f 1 ( 3 ) + f 2 ( 0 ) ) = f 1 ( 1 ) + f 2 ( 2 ) = 16 max(f_1(0)+f_2(3),f_1(1)+f_2(2),f_1(2)+f_2(1),f_1(3)+f_2(0)) =f_1(1)+f_2(2)=16 max(f1(0)+f2(3),f1(1)+f2(2),f1(2)+f2(1),f1(3)+f2(0))=f1(1)+f2(2)=16
,是最大收益
同样的用4元或者5元投资这两个项目,所带来的最大收益分别是21和26
我们接下来对问题进行建模:
目标函数:利用所分配的投资产生最大效益
约束条件:是在总资金的条件下进行投资
建模:
问题的解是向量 < x 1 , x 2 , ⋅ ⋅ ⋅ , x n > <x_1,x_2,···,x_n> <x1,x2,⋅⋅⋅,xn>
x i x_i xi是投给项目的钱数, i = 1 , 2 , ⋅ ⋅ ⋅ , n . i=1,2,···,n. i=1,2,⋅⋅⋅,n.
目标函数 m a x { f 1 ( x 1 ) + f 2 ( x 2 ) + ⋅ ⋅ ⋅ + f n ( x n ) } max \{f_1(x_1)+f_2(x_2)+···+f_n(x_n)\} max{f1(x1)+f2(x2)+⋅⋅⋅+fn(xn)}
约束条件: x 1 + x 2 + ⋅ ⋅ ⋅ + x n = m , x i ∈ N x_1+x_2+···+x_n=m,x_i\in N x1+x2+⋅⋅⋅+xn=m,xi∈N
下面我们来考虑用动态规划算法来解投资问题
动态规划算法来解投资问题
子问题的界定:由参数 k k k和 x x x界定
k k k:考虑对每个项目 1 , 2 , … , k 1,2,…,k 1,2,…,k的投资
x x x:投资总钱数不超过 x x x的
接下来我们再看一下递推方程
设 F k ( x ) : x F_k(x):x Fk(x):x元钱投给前k个项目的最大效益
我们可以这么想,假如我们要求 F k ( x ) F_k(x) Fk(x),即就是求x元钱投给前 k k k个项目的最大效益。那不妨求 p p p元钱 ( p ≤ x ) (p\leq x) (p≤x)投给前k-1个项目的最大效益 F k − 1 ( p ) F_{k-1}(p) Fk−1(p),进而确定 F k ( x ) F_k(x) Fk(x)
我们进而可以列出递推方程:
我们看到啊,
F
k
(
x
)
F_k(x)
Fk(x)的求解,就是去求用
x
−
x
k
x-x_k
x−xk分配前
k
−
1
k-1
k−1个项目所产生的最大效益。然而这个最大效益是在备忘录存着来。没错备忘录的作用就是存储最大的效益。
F
1
(
x
)
F_1(x)
F1(x):就是在投资表中的用x钱投资第一个项目的收益
接下来我们看个例子:
这是一个投资——效益表
我们要先明确最小子问题是什么,然后才能从这个最小子问题开始算起;然后考虑计算顺序,保证后面的值在前面已经计算好。
这里我们看到第一个项目的最大收益就是投资对应的收益,即
F
1
(
0
)
=
0
,
F
1
(
1
)
=
11
,
F
1
(
2
)
=
12
,
F
1
(
3
)
=
13
,
F
1
(
4
)
=
14
,
F
1
(
5
)
=
15
。
(1)
F_1(0)=0,F_1(1)=11,F_1(2)=12,F_1(3)=13,F_1(4)=14,F_1(5)=15。\tag{1}
F1(0)=0,F1(1)=11,F1(2)=12,F1(3)=13,F1(4)=14,F1(5)=15。(1)
我们能看到啊,前1个项目可以定为最小子问题,它的初值可以通过查表得到,不用计算。而后面的项目随着项目的增多,子问题的复杂性就会增强。因此我们根据项目序列的递增关系来计算,从而保证后面的值在前面已经计算好了。
我们通过上面的递推方程可以得知,用 x − x k x-x_k x−xk分配前 k − 1 k-1 k−1个项目所产生的最大效益越大以及x元钱投给前k个项目的最大效益越大,从而使得原问题的解达到最大。进而满足依赖关系。而对于 x k x_k xk为何值,这个是需要通过计算获取最优解来得到。
好,我们来继续看前两个项目的最大效益:
我们先看
x
=
0
x=0
x=0,则最大效益是0
再来看
x
k
=
1
x_k=1
xk=1,
F
2
(
1
)
=
m
a
x
{
f
2
(
1
)
+
F
1
(
1
−
1
)
,
f
2
(
0
)
+
F
1
(
1
−
0
)
}
=
11
F_2(1)=max\{f_2(1)+F_1(1-1),f_2(0)+F_1(1-0)\}=11
F2(1)=max{f2(1)+F1(1−1),f2(0)+F1(1−0)}=11
再来看
x
k
=
2
x_k=2
xk=2,
F
2
(
2
)
=
m
a
x
{
f
2
(
2
)
+
F
1
(
2
−
2
)
,
f
2
(
1
)
+
F
1
(
2
−
1
)
,
f
2
(
0
)
+
F
1
(
2
−
0
)
}
=
12
F_2(2)=max\{f_2(2)+F_1(2-2),f_2(1)+F_1(2-1),f_2(0)+F_1(2-0)\}=12
F2(2)=max{f2(2)+F1(2−2),f2(1)+F1(2−1),f2(0)+F1(2−0)}=12
再来看
x
k
=
3
x_k=3
xk=3,
F
2
(
3
)
=
m
a
x
{
f
2
(
3
)
+
F
1
(
3
−
3
)
,
f
2
(
2
)
+
F
1
(
3
−
2
)
,
f
2
(
1
)
+
F
1
(
3
−
1
)
,
f
2
(
0
)
+
F
1
(
3
−
0
)
}
=
16
F_2(3)=max\{f2(3)+F_1(3-3),f_2(2)+F_1(3-2),f_2(1)+F_1(3-1),f_2(0)+F_1(3-0)\}=16
F2(3)=max{f2(3)+F1(3−3),f2(2)+F1(3−2),f2(1)+F1(3−1),f2(0)+F1(3−0)}=16
同样的
F
2
(
4
)
=
21
,
F
2
(
5
)
=
26
F_2(4)=21,F_2(5)=26
F2(4)=21,F2(5)=26
当然,这里得到的 F 2 ( 1 ) , F 2 ( 2 ) , F 2 ( 3 ) , F 2 ( 4 ) , F 2 ( 5 ) F_2(1),F_2(2),F_2(3),F_2(4),F_2(5) F2(1),F2(2),F2(3),F2(4),F2(5)要记录到备忘录里面。
那么如何去记录解?
我们用
s
s
s数组来记录解。我们去记录在得到最大效益的时候,最后一个项目给了多少钱。
就如同上面的例子,在前两个项目的最大收益中。
x
i
(
x
)
:
x_i(x):
xi(x):分配x元钱给前i个项目,在最大收益时,第i个项目得到了多少钱
x
2
(
1
)
:
x_2(1):
x2(1):看到啊,
F
2
(
1
)
=
f
2
(
0
)
+
F
1
(
1
−
0
)
=
11
F_2(1)=f_2(0)+F_1(1-0)=11
F2(1)=f2(0)+F1(1−0)=11。此时,第2个项目得到了0元钱
x
2
(
2
)
:
f
2
(
0
)
+
F
1
(
2
−
0
)
=
12
x_2(2):f_2(0)+F_1(2-0)=12
x2(2):f2(0)+F1(2−0)=12。此时,第2个项目得到了0元钱
x
2
(
3
)
:
f
2
(
2
)
+
F
1
(
3
−
2
)
=
16
x_2(3):f_2(2)+F_1(3-2)=16
x2(3):f2(2)+F1(3−2)=16。此时,第2个项目得到了2元钱
同样的,我们也能得到
x
2
(
4
)
=
3
,
x
2
(
5
)
=
4
x_2(4)=3,x_2(5)=4
x2(4)=3,x2(5)=4
ok,下面介绍一下如何追踪解
上面的投资问题的结果如图所示:
我们细想,原问题是用5元钱分配所有项目(这里就是4个项目),所得到的最大收益
这个最大收益是不是就是
F
4
(
5
)
F_4(5)
F4(5)
(
F
4
(
5
)
F_4(5)
F4(5):用5元钱分配前4个项目得到的最大收益)
,那这个值就可以去衡量原问题的解。因此我们追踪解也要从
x
4
(
5
)
x_4(5)
x4(5)开始,自底向上追踪。
先看到x4(5)=1,说明达到最大收益的时候分配给最后一个项目,即第4个项目是1元钱。
那么第3个项目呢?
第3个项目就是
x
3
(
x
)
x_3(x)
x3(x),这个x就是5-1=4,就是用总共的5元钱-分配给第4个项目的钱数。
x
3
(
5
−
1
)
=
3
x_3(5-1)=3
x3(5−1)=3。因此在得到最大收益时,分配给第3个项目3元钱。
同理
x
2
(
4
−
3
)
=
0
,
x
1
(
1
−
0
)
=
1
x_2(4-3)=0,x_1(1-0)=1
x2(4−3)=0,x1(1−0)=1
也许你会问为什么要这么解?
我们看那个递推方程,我们既然知道
F
4
(
x
5
)
=
f
4
(
x
4
)
+
F
3
(
x
−
x
4
)
F_4(x_5)=f_4(x_4)+F_3(x-x_4)
F4(x5)=f4(x4)+F3(x−x4)。然而我们知道了在最大收益时,分配给第4个项目1元钱,这个可以通过代码可以实现。
则 F 3 ( 4 ) F_3(4) F3(4),这个通过查表即可得到41,此时分配给它的钱就是3。同样的也可以逆推出 F 2 F_2 F2和 F 1 F_1 F1中的 x 2 和 x 1 x_2和x_1 x2和x1。就是通过前k-1个项目的最大收益+用剩下钱分配给第k个项目的收益。然而前 k − 1 k-1 k−1个项目的最大收益是保存在了我们的备忘录中,所以这个值不仅可以查到,而且它只计算了一次。没有重复计算。使得这个唯一确定的值+ f 4 ( x 4 ) f_4(x_4) f4(x4)值就是最大收益。
因此我们抛去 f 4 ( x 4 ) f_4(x_4) f4(x4)的值,也就是前k个项目的最大收益 F 3 ( x − x 4 ) F_3(x-x_4) F3(x−x4)。
因此我们可以通过查表得到 x 3 ( x − x 4 ) x_3(x-x_4) x3(x−x4)。故这样计算是合理的
#include<bits/stdc++.h>
using namespace std;
#define MAX_N 4 //最大投资项目数目
#define MAX_M 5 //最大投资钱数(万元)
//f[i][j]的意义:第 i(从 1 开始)个项目投资 j 万元的收益
int f[MAX_N+1][MAX_M+1] = {
{0,0,0,0,0,0},
{0,11,12,13,14,15},
{0,0,5,10,15,20},
{0,2,10,30,32,40},
{0,20,21,22,23,24}
};
void printNum(int num[MAX_N+1][MAX_M+1]){
for(int i=0;i<=MAX_M;i++)
{
for(int j=0;j<=MAX_N;j++)
{
cout << num[j][i] << " ";
}
cout << endl;
}
return;
}
int main()
{
int s[MAX_N+1][MAX_M+1] = {0};//s数组用于记录最后一个项目分配的钱
int F[MAX_N+1][MAX_M+1] = {0};
for(int i=0;i<=MAX_M;i++){
F[1][i] = f[1][i];
s[1][i] = i;
}
for(int i=2;i<=MAX_N;i++)
{
for(int j=0;j<=MAX_M;j++)
{
int max = F[i-1][j];
int cnt = 0;
for(int k=0;k<=j;k++)
{
int num = f[i][k]+F[i-1][j-k];
if(num>=max)
{
max = num;
cnt = k;
}
}
F[i][j] = max;
s[i][j] = cnt;
}
}
printNum(f);
cout << endl;
printNum(s);
cout << endl;
printNum(F);
cout << endl;
int res = s[MAX_N][MAX_M];
int pro = MAX_N;
int m = MAX_M;
while(pro>0)
{
cout << "第" << pro << "个项目的钱数为:" << res << endl;;
pro-=1;
m-=res;
res = s[pro][m];
}
return 0;
}