1 1,1
3 2 2,1 2,2
4 10 1 3,1 3,2 3,3
4 3 2 20 4,1 4,2 4,3 4,4
(a)数字三角形 (b)状态编号
从第一行的数开始,每一次可以向下或者向右走一格,一直走到最下行,把沿途的数全部加起来,如何使这个和最大?
如果熟悉回溯法,会发现这是一个动态决策问题,每次有两种选择,左下或者右下.用回溯法求出所有可能的路线,就可以从中选择最优路线,但是有路线2的n次方条,效率不可忍受
可以用抽象的方法来思考问题,把当前位置看成一个状态,用(i,j)位置来表示.然后定义状态(i,j)的指标函数,d(i,j)为从格子(i,j)出发出发能获得的最大值
看不同的状态如何转移,往左走,则走到(i+1,j),往右走,则走(i+1,j+1),自由选择,选择其中较大的一个
状态转移方程 d(i,j)=a(i,j)+max{d(i+1,j),d(i+1,j+1)}
如果从(i+1,j)出发得到部分和是最大的,则加上a(i,j)之后也是最大的 .这个性质叫做最优子结构
动态规划的核心是状态和状态转移方程.
实现计算
1.递归计算
int d(int i,int j)
{
return a[i][j]+(i==n? 0:d(i+1,j)>d(i+1,j+1));
}
效率太低,递归时调用关系树造成了重复计算
2.递推计算
int i,j;
for(j=1;j<=n;j++)
d[n][j]=a[n][j];
for(i=n-1;i>=1;i—)
for(j=1;j<=i;j++)
d[i][j]=a[i][j]+d[i+1][j]>?d[i+1][j+1];
递推的时间复杂度为 状态总数x每个状态的决策个数x决策时间
3.记忆化搜索
int d(int i,int j)
{
if(d[i][j]>=0)
return d[i][j];
return d[i][j]=a[i][j]+(i==n?0:d(i+1,j)>?d(i+1,j+1));
}
把计算结果保存在d[i][j]中,每次记录是否计算过
也可以用另一个数组vis[]保存是否被计算过
0-1背包问题
n种物品,每种有无穷多个,第i种体积为vi,重量是wi,选一些到容量为c的背包中,使得背包内物体在总体积不超过c的前提下重量尽量大
刚才的方法已经不适用了,原来的状态转移太混乱了.要消除这种混乱,要让决策有序化.这就是多段决策问题
每次做一次决策就可以得到解的一部分.所有的决策做完就得到最终解.回溯法中每个决策对应一颗子树,每个解答书对应一个解.节点层数是下一个待填充位置cur,就是即将完成的决策序号,在动规中称为阶段
多段决策可用动态规划解决.d(i,j)表示第i层,背包剩余容量为j的最大重量和.
d(i,j)=max{d(i+1,j),d(i+1,j-V[i])+w[i]}
边界是i>n时d(i,j)=0,j<0时为负无穷
白话一点,d(i,j)表示把第i,i+1,i+2,,,n个物品装到容量为j的背包中的最大重量和,事实上,这个说法常用阶段和层这样的术语,
代码(结果是d[1][c])
for(int i=n;i>=1;i—)
for(int j=0;j<=c;j++)
{
d[i][j]=(i==n? 0:d[i+1][j]);
if(j>=v[i])
d[i][j]>?=d[i+1][j-v[i]]+w[i];
}
i必须逆序枚举,但j的循环次序是无关紧要的
还有一种对称的状态定义,用f(i,j)表示把前i个物品装到容量为j的背包中的最大重量和
状态转移方程: f(i,j)=max{f(i-1,j),f(i-1,j-v[i])+w[i]}
最终答案为g(n,C)
for(int i=1;i<=n;i++)
for(int j=0;j<=C;j++)
{
f[i][j]=(i==1? 0:f[i-1][j]);
if(j>=v[i])
f[i][j]>?=f[i-1][j-v[i]]+w[i];
}
动态规划的类型很多,今天就介绍最优子结构和多段决策,还有树形动规,集合动规,DAG动规日后为一一研究介绍