## 问题描述:
1.什么是动态规划算法
2.动态规划算法为何能带来效率上的提升
3. 动态规划算法的特征
## 解决方案:
问题1: 什么是动态规划算法
回答:大约在60多年前,动态规划算法开始出现并大规模使用,动态规划算法的英文名称为dynamic programming,最初用于解决多阶段决策问题,事实上这正是动态规划算法最经典的模型,在此,给出对动态规划算法的定义,如果某问题可以以时间或空间或其他事物将之划分为多个阶段的任务,并且这些任务在以某种“时序”推进时,满足最优子结构性质和无后效性,则可应用动态规划算法。
问题2: 动态规划算法为何能带来效率上的提升
回答: 动态规划算法并不神奇,实际问题中,往往存在许多的重叠子问题,动态规划算法并不盲目搜索,在求得某个子问题时,记录当前子问题的状态,这样,在需要使用这个子问题时,便能直接得出一部分的结果
问题3: 动态规划算法的特征
回答:许多算法书中都有提到,动态规划算法具有最优子结构性质,这并不够完善,如果我们希望使用动态规划算法解决实际问题,首先需要思考某个大的问题是否可以由多个子问题递推得到,其次,需要检查分解成子问题时,是否存在大量重叠的子问题,即当我们希望使用动态规划算法,首先这个问题应该具有大量重叠子问题,然后考虑以某种“时序”推进,即确定阶段,阶段的划分是困难的,因为我们的阶段划分之后需要满足最优子结构,即如果我们以某种策略进行阶段的转移,求得最优解,那么取出阶段转移的一部分,它也能求得此阶段的最优解,最后是考察无后效性问题,当我们进行状态转移时,前面做出的决策是否影响了后面的状态,使子问题产生了改变,动态规划算法必须满足无后效性,即前面做出的决策不能影响后续的决策
## 文章目标: 以具体的语境来帮助读者理解应用动态规划算法的条件,理解重叠子问题,最优子问题,无后效性,状态转移等概念
## 语境描述: 有一只老鼠非常喜欢吃奶酪,于是它在每一个地洞都放置了一些奶酪,地洞正好是一个N*N的矩阵,每个洞里的奶酪数量是f[i][j],有一只猫总盯着这只老鼠,现在已知老鼠只能在矩阵中横着走或纵着走,每次最多走C步,因为老鼠一旦吃了奶酪后会变重,因此为了保证能量奔跑不被猫抓住,它下一次吃的奶酪一定要多于上一次吃的奶酪,老鼠最初在第一行第一列,问老鼠最多可以吃到多少奶酪(N <= 100)
样例输入
3 1
1 2 5
10 11 6
12 12 7
样例输出
37
样例描述,3*3的矩阵,老鼠一次最多走一步,老鼠先吃1,再吃2,再吃5,再吃6,再吃11,再吃12,奶酪总数为37
## 解决方案:
有一定编程基础的人,应该可以很快想到用深度优先搜索算法来解决上述问题,从某点出发,进行状态转移,满足下一点的奶酪数一定要大于当前点,当无法继续搜索时得到一组解决方案,遍历所有解决方案取最优解,这显然是正确的,效率上非常低下,在100*100的数据规模下,算法无法承受。
思考我们深度优先搜索的过程,点i到达点j后,点j无论如何也不会搜索点i了,因为下一次搜索的点的奶酪数必须比上一次多,如果我们以每一步为阶段划分的话,这满足了无后效性(前面做出的决策不影响后面的决策,不管(1,1)如何到达状态(x,y),从(x,y)出发的搜索不受(1,1)到(x,y)任何一次决策的影响),是否存在大量重叠子问题呢?这是显然的,对于从(x,y)出发的搜索,最优解是一定的,因此每当搜索到(x,y)时,完全不需要多次搜索,深度搜索中大量的子问题重叠了(深度搜索每次都要尝试从(x,y)出发继续寻找最优解),是否具有最优子结构呢?这更加是显然的了,对于最终的决策而言,它的任意一段决策也是最优的选择的,样例中从(1,1)出发的最优解是最优的,从它的某一个状态如f[1][3]=5出发,同样可以得到从(1,3)出发的最优解
综上所述,上述问题完全可以使用动态规划算法,以下用代码讲解状态的转移和动态规划非常重要的使用思想-记录状态的搜索
#include
#include
#include
#include
using namespace std;
const int maxn = 105;
const int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
int G[maxn][maxn],dp[maxn][maxn],n,k; //dp[i][j]记录从(i,j)出发能得到的最优解
void dfs(int i,int j)
{
int res = G[i][j];
for(int x = i - 1; x >= 0 && i - x <= k; x--)
{
if(G[i][j] >= G[x][j]) continue;
if(dp[x][j] == -1) dfs(x,j);
res = max(res,G[i][j] + dp[x][j]);
}
for(int x = i + 1; x < n && x - i <= k; x++)
{
if(G[i][j] >= G[x][j]) continue;
if(dp[x][j] == -1) dfs(x,j);
res = max(res,G[i][j] + dp[x][j]);
}
for(int y = j - 1; y >= 0 && j - y <= k; y--)
{
if(G[i][j] >= G[i][y]) continue;
if(dp[i][y] == -1) dfs(i,y);
res = max(res,G[i][j] + dp[i][y]);
}
for(int y = j + 1; y < n && y - j <= k; y++)
{
if(G[i][j] >= G[i][y]) continue;
if(dp[i][y] == -1) dfs(i,y);
res = max(res,G[i][j] + dp[i][y]);
}
dp[i][j] = res;
}
int main()
{
while(scanf("%d%d" ,&n,&k) != EOF)
{
if(n == -1 && k == -1) break;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
scanf("%d" ,&G[i][j]);
}
}
memset(dp,-1,sizeof(dp));
dfs(0,0);
printf("%d\n",dp[0][0]);
}
return 0;
}
## 文章目标
在学习了动态规划的重要概念后,当然需要用实践来检验,以下是我完成的有争议的习题之一,网上给出的动态规划算法我均无法证明算法的正确性,在此我给出我论证正确的贪心算法
## 问题描述1
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.
Input输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)
Output对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.
Sample Input8 389 207 155 300 299 170 158 65
Sample Output 2
问题难以确定一个恰当的阶段,使问题满足最优子结构性质和无后效性,事实本题满足贪心性质,网上绝大部分此题的动态规划解法均存在问题,这里给出我的贪心代码(可使用数学归纳法证明正确)
#include#include #include #include #include using namespace std; const int INF = 0x3f3f3f3f; int main() { int n; while(scanf("%d" ,&n) != EOF) { vector<int> vec; for(int i = 0; i < n; i++) { int x; scanf("%d" ,&x); if(vec.size() == 0) vec.push_back(x); else { int minx = INF,index = -1; for(int j = 0; j < vec.size(); j++) { if(vec[j] >= x && vec[j] - x < minx) index = j,minx = vec[j] - x; } if(index == -1) vec.push_back(x); else vec[index] = x; } } printf("%u\n",vec.size()); } return 0; }