动态规划的递归写法与递推写法
多训练,多思考,多总结是学习动态规划的重点
什么是动态规划
动态规划是一种用来解决一类最优化问题的算法思想
一般可以使用递归或递推的写法来实现动态规划
动态规划的递归写法
如果一个问题可被分解为若干子问题[分治]
且子问题会重复出现[重叠子问题]
动态规划下,记录问题的解答,使得下次碰到相同的子问题时直接使用之前记录的结果,避免不必要的重复计算[算法性能优化策略]
动态规划的递推写法
将一些数字排成数塔的形状
其中第一层有一个数字,
第二层有两个数字,
...
第n层有n个数字
现在要从第一层走到第n层
每次只能走向下一层连接的两个数字中的一个
问:
最后将路径上所有数字相加后得到的和最大是多少?
如开一个二维数组f
其中f[i][j]存放i层的第j个数字
不妨令dp[i][j]表示从第i行第j个数字出发的到达最底层的所有路径中能得到的最大和
定义了这个数组后dp[1][1]就是最终想要的答案
dp[1][1]=max(dp[2][1], dp[2][2]) + f[1][1]
d[i][j]=max(dp[i+1][j], dp[i+1][j+1]) + f[i][j]
把dp[i][j]称为问题的状态,把上面的式子称作状态转移方程
数塔的最后一层的dp值总是等于元素本身
即dp[n][j]==f[n][j](1<=j<=n)
把这种可直接确定结果的部分叫边界
动态规划的递推写法总是从这些边界出发,通过状态转移方程扩散到整个dp数组
如果一个问题的最优解可由子问题最优解构造,称有最优子结构
最优子结构[最优化的分治]
重叠子问题[效率优化的凭借]
-->效率优化的最优化分治=动态规划
贪心:
是某些最优子结构+重叠子问题情形,
可证明,每一次可作特定选择,结合一个子问题得到原问题的解答,
属于依据问题性质,只处理部分而得到结果的情况.[更强的效率优化]
最大连续子序列和
给定一个数字序列A_{1}, A_{2}, ... , A_{n}
求i, j(1 <= i <= j <= n)
使得A_{i}+...+A_{j}最大
输出这个最大和
- 令状态dp[i]表示以A[i]作为末尾的连续序列的最大和[这里说A[i]必须作为连续序列的末尾]
通过设置这么一个dp数组,要求的最大和其实就是dp[0], dp[1], ... , dp[n-1]中的最大值
- 作如下考虑
因为dp[i]要求必须以A[i]结尾的连续序列
则只有两种情况
1,这个最大和的连续序列只有一个元素,即以A[i]开始,以A[i]结尾
2,这个最大和的连续序列有多个元素,即从前面某处A[p]开始(p < i),
一直到A[i]结尾
对第一种情况,最大和就是A[i]本身
对第二种情况,最大和是dp[i-1]+A[i],即A[p]+...+A[i-1]+A[i]=dp[i-1]+A[i]
由于只有这两种情况,于是得到状态转移方程
dp[i]=max{A[i], dp[i-1]+A[i]}
且边界为dp[0]=A[0]
由此从小到大枚举i,即可得到整个dp数组
接着输出dp[0],...,dp[n-1]中的最大值即为最大连续子序列的和
状态无后效性是指:
当前状态记录了历史信息,
一旦当前状态确定,
就不会再改变
且未来的决策只能在已有的一个或若干个状态的基础上进行
历史信息只能通过已有的状态去影响未来的决策
设计状态和状态转移方程是动态规划的关键
最长不下降子序列[LIS]
在一个数字序列中,
找到一个最长的子序列[可以不连续]
使得这个子序列是不下降的[非递减的]
令dp[i]表示以A[i]结尾的最长不下降子序列长度
这样对A[i]来说会有两种可能
- 如存在A[i]之前的元素A[j](j < i)
使得A[j]<=A[i]
且dp[j]+1>dp[i]
则把A[i]跟在以A[j]结尾的LIS后面,形成一条更长的不下降子序列
- 如A[i]之前的元素都比A[i]大
则A[i]自己形成一条LIS,但长度为1
最后以A[i]结尾的LIS长度是1,2中能形成的最大长度
dp[i]=max{1,dp[j]+1}
(j=1,2,...,i-1&&A[j]<A[i])(0<=i<=n, 0<=j<=m)
最长回文子串
给出一个字符串S,求S的最长回文子串的长度
令dp[i][j]表示S[i]至S[j]所表示的子串是否是回文子串,是则1,不是为0
根据S[i]是否等于S[j],把转移分为两类
- 若S[i]==S[j],只要S[i+1]至S[j-1]是回文子串,S[i]至S[j]就是回文子串
如S[i+1]至S[j-1]不是回文子串,则S[i]至S[j]也不是回文子串
- 若S[i]!=S[j],则S[i]至S[j]一定不是回文子串
边界:
dp[i][i]=1,dp[i][i+1]=(S[i]==S[i+1])?1:0
递归写法总是从小规模到大规模,特点大规模所依赖的小规模全部已经求解
DAG最长路
DAG是有向无环图
DAG最长路径--关键路径
DAG最长路和最短路思想是一致的[本节解法比之前介绍的关键路径解法简单]
本节着重解决两个问题
- 求整个DAG中的最长路径
- 固定终点,DAG的最长路径
给定一个有向无环图,怎样求解整个图的所有路径权值之和最大的那条
令dp[i]表示从i号顶点出发能获得的最长路径长度
所有这样dp[i]的最大值就是整个DAG的最长路径长度
如果从i号顶点出发能直接达到顶点j_{1}, j_{2}, ... , j_{k}
而dp[j_{1}], dp[j_{2}],...,dp[j_{k}]均已知
就有dp[i]=max{dp[j]+length[i->j]|(i,j)属于E}
按逆拓扑顺序求解[保证求解本规模问题时,所有更小规模问题都已求解]
递归求解无此限制
模仿字符串来定义路径序列的字典序
如有两条路径
a_{1}->a_{2}->...->a_{m}
与b_{1}->b_{2}->b_{n}
且
a_{1}=b_{1}
a_{2}=b_{2}
...
a_{k}=b_{k}
a_{k+1}<b_{k+1}
则称路径序列a_{1}->...->a_{m}的字典序小于路径b_{1}->b_{2}->...->b_{n}
可提出一个问题,
如果DAG中有多条最长路径,
如何选取字典序最小的那条
遍历i的邻接点顺序按从小到大即可
- 固定终点,求DAG的最长路径长度
假设规定的终点为T
那么可令dp[i]表示从i号顶点出发到达终点T能获得的最长路径长
同样的,如果从i号顶点出发能直接达到顶点j_{1}, j_{2}, ... ,j_{k}
而dp[j_{1}], dp[j_{2}],...,dp[j_{k}]均已知
就有dp[i]=max{dp[j]+length[i->j]|(i,j)属于E}
第一个问题中没有固定终点,所有出度为0的顶点的dp为0,是边界
本问题指明了终点,故边界为dp[T]=0
int DP(int i)
{
if(vis[i])
return dp[i];
vis[i]=true;
for(int j = 0; j < n; j++)
{
if(G[i][j] != INF)
{
dp[i]=max(dp[i], DP(j)+G[i][j]);
}
}
return dp[i];
}
考虑记录所有方案
选择字典序最小的方案
矩形嵌套
给出n个矩形的长和宽,
定义矩形的嵌套关系为:如果两个矩形A和B
其中矩形A的长和宽分别为a,b
矩形B的长和宽分别为c,d
且满足a<c,b<d
或a<d,b<c
则称矩形A可嵌套于矩形B内
现在要求一个矩形序列
使得这个序列中任意两个相邻的矩形都满足前面的矩形可以嵌套于后一个矩形内
且序列的长度最长
如有多个这样的最长序列,选择矩形编号序列字典序最小的那个
将每个矩形看成一个顶点,
将嵌套关系视为顶点间的有向边,
边权均为1
就可转换为DAG最长路问题
背包问题
01背包
完全背包
多阶段动态规划问题
一类问题,
可描述成若干个有序的阶段
且每个阶段的状态之和上一个阶段的状态有关
01背包问题
有n件物品
每件物品的重量为w[i],价值为c[i]
现有一个容量为V的背包
问如何选取物品放入背包,使得背包内物品的总价值最大,其中每种物品只有1件
令dp[i][v]表示前i件物品[1<=i<=n,0<=v<=V]恰好装入容量为v的背包中所能获得的最大价值
- 对第i件物品的选择策略,有两种
1.不放入第i件物品,则问题转化为前i-1件物品恰好装入容量为v的背包所能获得的最大价值即为dp[i-1][v]
2.放第i件物品,则问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,即dp[i-1][v-w[i]] + c[i]
基于上述得到状态转移方程
通过边界dp[0][v]=0
就可把整个dp数组递推出来
最后枚举dp[n][v]取其最大值就是最后的结果
完全背包问题
有n中物品,
每种物品的单件重量为w[i]
价值为c[i]
现有一个容量为V的背包,
问如何选取物品放入背包,使得背包内物品的总价值最大
其中每种物品都有无穷件
令dp[i][v]表示前i件物品恰好放入容量为v的背包能获得的最大价值
完全背包问题的每种物品有两种策略
对第i件物品来说:
- 不放第i件物品,则dp[i][v]=dp[i-1][v]
- 放第i件物品,完全背包选择放第i件物品后,转移到dp[i][v-w[i]]
因为每种物品可放任意件
放了第i件物品后,还可继续放第i件物品
直到第二维的v-w[i]无法保持大于等于0为止
边界:dp[0][v]=0,0<=v<=V
总结