在假期结束之前,总结一下假期的收获——动态规划。
其中会简单和分治法进行比较。
动态规划
让我们先看一下百度百科上对动态规划的定义:
动态规划是运筹学的一个分支,是求解决策过程最优化的数学方法,它把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。
按照我的理解,也就是把大事化小,小事化无,把问题划分成我们方便处理的状态,也就是dp问题
。
dp中的几个常见概念:
- 阶段。把问题分成几个相互联系的有顺序的几个环节。
- 状态。某一阶段的出发位置。通常一个阶段包含若干状态。
- 决策。从某阶段的一个状态演变到下一个状态的选择。
- 策略。有起点到终点的全过程中,由每个决策组成的决策序列。
- 状态转移方程。由i阶段到i+1阶段状态的演变规律。
dp适用的基本条件:
- 具有相同子问题。一个问题能够分解出几个子问题,而这些子问题也能分解为新的相同的子问题,即一个问题能被分解为A、B、C,那么A、B、C分别也能分解为A、B、C,却不能分解为D、E、F。
- 满足最优子结构。问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。
- 满足无后效性。“过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结”,也就是当前问题的决策不能对后来问题的决策产生影响,状态出现在策略任何一个位置,它的地位相同,都可实施同样策略。
解决dp问题的核心:构造递推式,也可以说是状态转移方程,如F(n) = F(n-1) + F(n-2)
。
个人感觉这是整个dp问题最难的地方。
解决dp问题的核心方法:自底向上(递推)或者自顶向下(记忆化搜索)。
为了减少灌水,这里贴两篇文章,一篇很好的说明了解决dp问题的核心方法,一篇形象的介绍了dp,良心推荐。
和分治法的比较
分治,“分而治之”,也就是把一个难以直接解决的大问题,分割成一些规模比较小的相同的小问题,最后合并结果,划分为分解—>解决—>合并三个步骤。
分治法是将问题划分成一些独立的子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。而动态规划适用于子问题独立且重叠的情况,也就是各子问题包含公共的子 子问题。
具体实现
常用解决dp问题步骤:
- 找到一个原问题,并分析它的子问题。量化问题;
- 根据原问题和子问题确定状态。可以根据问题中的变量来判断有几个状态;
- 确定状态转移方程。额外注意一下临界的特殊条件;
- 确定编程实现方式。看是选择从上到下还是从下到上。
以最常见的背包问题为例。
有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
分析问题可以得出,其中的变量是物品编号i和背包剩余容量j(常量是物品的价值w和总容量V);
确定状态f[i][j],表示前i件物品放入一个容量为j的背包可以获得的最大价值 ;
确定状态转移方程,由i-1的结果判断放不放i,放的话f[i][j] = f[i-1][j-v[i]]+w[i]
,不放的话f[i][j] = f[i-1][j]
,最后可以得出f[i][j] = max{f[i-1][j], f[i-1][j – v[i]] +w[i]}
;
最后编程实现:
#include<iostream>
#include<algorithm>
#define maxSize 10001
using namespace std;
int main(){
int n,v,i,j;
int vol[maxSize],val[maxSize],f[maxSize];
memset(f,0,sizeof(f));
cin>>n>>v;
for(i = 0;i < n;i++){
cin>>vol[i]>>val[i];
}
for(i = 0;i < n;i++){
for(j = v;j >= vol[i];j--){
if(f[j-vol[i]]+val[i] > f[j]){
f[j] = f[j-vol[i]] + val[i];
}
}
}
cout<<f[v]<<endl;
//system("pause");
return 0;
}
时间复杂度O(VN),空间复杂度O(VN) 。
更多解看这里,有问题会再修改的。