动态规划基本思想是将原问题分解为若干个重叠子问题,每个子问题的求解过程作为一个阶段,完成前一个阶段后,根据前一个阶段的结果求解下一个阶段。
动态规划原理
动态规划解决的问题,需要满足三个条件:最优子结构,无后效性和子问题重叠。
最优子结构
动态规划解决一个问题的第一步是规划一个最优解的结构。
如果一个问题的最优解中包含了一个或多个子问题的最优解,则该问题具有最优子结构性质。最 优子结构是使用动态规划的最基本条件,如果不具有最优子结构性质,就不能使用动态规划。
无后效性
已经求解的子问题,不会再受到后续决策的影响。
子问题重叠
如果有大量的重叠子问题,那么只需要求解一次,然后把结果存储在表中,以后使用时可以直接查表,避免重复求解。子问题重叠不是使用动态规划的必要条件,但其可彰显动态规划的优越性。
基本思路
动态规划把原问题划分为若干个重叠子问题,然后通过求解子问题的解得到原问题的解,每个子 问题的求解过程都构成一个“阶段”。在完成前一个阶段的计算后才会执行下一个阶段的计算。
动态规划的状态空间构成一个有向无环图,求解遍历的顺序就是该有向无环图的一个拓扑序。
有向无环图中节点对应问题的“状态”,图中的有向边对应状态之间的转移,转移的选择就是动态规划中的“决策”。
状态,阶段,决策构成动态规划的三要素。
(1) 状态表示
将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来,确定状态和状态变量。
(2) 阶段划分
按照问题的时间或空间特征,把问题划分为若干个阶段。在划分阶段时,
注意划分后的阶段一定是有序或者可排序的,否则问题无法求解。
(3) 状态转移方程
状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。根据相邻两段的各个状态之间的
关系来确定决策,一旦确定了决策,就可以写出状态转移方程。
(4) 边界条件
状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
(5) 求解目标
确定问题的求解目标,然后根据状态转移方程的递推结果求解目标
具有最优子结构也可能是适合用贪心的方法求解。
动态规划的时候不可能在子问题还没有得到最优解的情况下就做出决策,
而是必须等待子问题得到了最优解之后才对当下的情况做出决策,
所以往往动态规划都可以用 一个或多个递归式来描述。
而贪心算法却是先做出一个决策,然后在去解决子问题。这就是贪心和动态规划的不同。
线性动态规划
https://blog.csdn.net/sigd/article/details/127315193
记忆化搜索:
求解每一个状态,先判断该状态是否曾经求解过,如果曾经求解过,直接拿过来使用;
如果没求解过,递归求解,并存储。
//洛谷P1048 采药
#include <iostream>
#include<string.h>
using namespace std;
const int MAXN = 101;
const int MAXT = 1001;
//提取状态m[n][t]: n个物品,剩余时间t,可获得的最大价值
int m[MAXN][MAXT];
int cost[MAXN];
int val[MAXN];
int dfs(int n, int t)
{
int ans = 0;
if (n == 0)
return 0;
if (t <= 0)
return ans;
if (m[n][t] != -1)
return m[n][t];
if (t < cost[n]) // 剩余时间太少,只能跳过当前物品
ans = dfs(n-1, t);
else
ans = max(dfs(n-1, t), dfs(n-1, t-cost[n]) + val[n]); // 当前物品是否被选
m[n][t] = ans;
return ans;
}
int main()
{
int n, t;
cin >> t >> n;
for (int i = 1; i <=n; ++i)
cin >> cost[i] >> val[i];
memset(m, -1, sizeof(m));
int ans = dfs(n, t);
cout << ans << endl;
return 0;
}