动态规划阶段性总结(一)
动态规划与递推法(一)
从别的大佬那里学来可以写博客来记录自己的学习,那么我就尝试写一次
一、动态规划(递推法)的基本思想
这里说一下动规的基本思想,本思想仅仅截止于2020.6.26日,今后我如果对动规有新理解,将会持续更新。
首先动规讲究两个原则:
(我感觉目前我自己对这两个原则理解还不是很深刻,因为做的题还不够,就仅仅讲讲目前的理解,以作为记录)
1、最优化原则
一本通上有一句话说的很好:
一个问题的局部最优将导致整个问题最优
动态规划我目前遇到的题基本上都是用来解决最优解问题,例如找出最少花费啊,最优路径啊等等等等。而在解题的过程中,肯定是一步步求解,每一步就相当于一个子问题,在做题时你就会发现,你的每个子问题也是按着最优解去求的,所以最终的结果一定是最优的。
此处必须要提一下贪心法,有的小伙伴(包括我)之前也一直认为贪心法的每一步也是最优解,因为每次都会去找一个最好的嘛,但其实不然,贪心的局部看似最优,但最后往往得不到整体最优,所以局限性很大。具体与贪心的区别我以后大概会写吧
2.无后效性原则
一本通上又一句话说得好:
某阶段的状态一旦确定,则此后的过程演变不再受此前各状态及决策的影响
我的理解是什么呢,就是某一个决策一旦确定,那么你的这个决策一定是最优的,且对整体而言也是最优的,你以后的数据,决策绝对不会对之前的决策有任何的影响,但是你的之前的这个决策会对后面你即将做出的决策产生影响,这是一定的,毕竟一步步算嘛,前人铺路,后人乘凉嘛。
简单地说,决策一旦下达,便是一锤定音,不会再更改
如果你在写动规的过程中,遇到了前后决策纠缠不清的情况,大概要重新梳理一下思路了。
二、具体的例题上一上呗~
以下题目来自奥赛一本通,评测网站:http://ybt.ssoier.cn:8088/index.php
1、数字金字塔
【题目描述】
观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
【题目描述】
第一个行包含R(1≤ R≤1000),表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
【输出】
单独的一行,包含那个可能得到的最大的和。
【输入样例】
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
【输出样例】
86
【思路】
哈哈
这个题可有意思了,作为入门引例真的再合适不过了
首先第一想法是不是贪心法亚
对嘛,我每一次都选个大的加上去,结果不就大了吗!
很遗憾,不对呀 阿sir~~
你康康,加入从第一个开始,每一次向下都选择数字最大的,那么就是
13 --> 11 --> 12 --> 14 -->13 == 63 < 86
不对呀,是吧。所以看来只能用动态规划了。
这道题告诉我们做人不要贪心亚,只顾眼前利益而不看长远,这定律感觉在各个领域都适用呢~~
那么问题来了,动态规划怎么搞呢?
这里我说一下正推法,至于倒退法请读者(我自己)自己去想!!!!(想不明白看书!!)
首先 我们要设立一个数组 b[i][j] ,存放一个很关键的东西:
当前决策的最优解的值
然后我们一步一步看
当我们从最顶部开始:13 此时没有别的点,就他一个,那么此时最优解就是他自己 b[1][1]=13
然后看第二层:11 和 8 此时两者都只连接了一个 13 所以此时这两个点的最优解 是他们自己的值加上之前确定好的最优值13
即
b[2][1]=13+11=24
b[2][2]=13+8=21
然后第三层
12这个点,只有一条路到达他 所以 b[3][1]=b[2][1]+12=45
7这个点,有两条路可以到达他 由于之前的决策已经确定,一定为最优解,所以选择一个最大的即可:b[3][2]=b[2][1]+7=31
(有同学可能会问了,这里你也是选最大的啊,为啥就不叫贪心法了呢?这里最大的区别就是上面的决策是已经确定好了的最优解,所以我敢直接加上去,这就是最微妙之处,请你后来看的时候务必细细体会这里,这里就是利用了无后效性原则,我敢加就是因为我知道后面的决策不会对前面有影响,但贪心就不一样了,后面的可能就会对前面造成影响,使其不是最优解)
26这个点,也只有一条路可以到达
b[3][3]=b[2][2]+26=47
按着这个思想,一步一步向下类推,最后的最大值就是答案咯
下面上代码啦!(你一定能看明白的w!!! )
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
int main(){
int a[100][100];//数塔
int b[100][100];//已算好的状态最优值
int d[100][100];//用于记录路径,仅有0和1,0为向下走,1为向右走
int n,maxx=0;
scanf("%d",&n);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>a[i][j];
b[i][j]=a[i][j];
}
}
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
if(b[i-1][j-1]>b[i-1][j]){
b[i][j]=a[i][j]+b[i-1][j-1];
d[i-1][j-1]=1;
}
else {
b[i][j]=a[i][j]+b[i-1][j];
d[i-1][j]=0;
}
}
}
int temp;
for(int i=1;i<=n;i++){
if(b[n][i]>maxx){
maxx=b[n][i];
temp=i;
}
}
cout<<maxx<<endl;
/*cout<<a[1][1]<<"-->";
int t=1;
for(int i=2;i<=n-1;i++){
t=t+d[i-1][t];
cout<<a[i][t]<<"-->";
}
cout<<a[n][temp]<<endl;
//return 0;*/
}
这里小小总结一下:
如果你觉得这个题能用动规做,那么一定就能找到一个最原始的一个部分,他的最优值是已经确定下来的,作为第一步决策,就像上面题中的第一个点13,然后再顺着一步一步的走下去。
另外,所给数据一定是有顺序的,或者是可以排序的,这样你才能写循环一步一步去求解。
(还有一个最长不下降子序列的题目,今天太晚了,明天写!!!!! )