从今天开始学习动态规划,个人觉得这是一个比较难的问题。我一直不敢做,但是为了这次能够参加省赛不得不去学了。看着以前的博文,觉得自己懂得知识好少,还有很多虽然写了,但是还是懵懵懂懂,算了,还是一步步来,以后再去深挖吧,最起码先要懂有这回事吧~~~加油!!
动态规划一般可分为线性DP(导弹拦截),区域DP(石子合并),树形DP(二分查找树),背包DP(背包问题,装箱问题)四类。
动态规划常用来求解决策过程中最优化的问题。
但是这个算法相比别的方法而言,它有章可循,一般的步骤为:
(1)找出最优解的性质,并刻画其结构特征;
(2)递归地定义最优解(写出状态转移方程)。
(3)以自顶向下或自底向上的方式算出最优解。
最难的部分一般都是写出状态转移方程,可以说这个步骤如果出来了,这个程序大概也就出来了。
状态转移方程:用来表示前后阶段关系的方程。有点像高中数列中的求通项公式。
使用动态规划的算法:最长单调子序列,最长公共子序列,Floya-Warshall算法,Viterbi算法
理论东西不多说,可以自己找下其它资料,这里直接用题目进行分析:
袁老师是以这个题目为例题讲的NYOJ 18(数塔),题目的意思是:从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
可以采用自顶向下的算法或自底向上算法。
对于这道题一般都采用自底向上的算法,因为按照这种算法的话最后的第一层只有一个数,直接输出即可。
解法一:自底向上算法,倒推,每一步保存(i,j)位置与下面相邻的两个数(位置为(i+1,j)和(i+1,j+1))的最大值的和.一直到第一层即可。状态转移方程为:DP[i][j]+=max(DP[i+1][j+1],DP[i+1][j+1]),然后输出DP[0][0];
#include<iostream> #include<cstring> using namespace std; const int MAX=110; #define max(a,b) a>b?a:b #define CLR(arr,val) memset(arr,val,sizeof(arr)) int n,DP[MAX][MAX]; int main() { cin>>n; CLR(DP,0); for(int i=0;i<n;i++) for(int j=0;j<=i;j++) cin>>DP[i][j]; for(int i=n-2;i>=0;i--) for(int j=0;j<=i;j++) DP[i][j]+=max(DP[i+1][j],DP[i+1][j+1]); cout<<DP[0][0]<<endl; return 0; }
解法二:自顶向下算法,保存肩上的相邻两个数的最大值的和,状态转移方程为:DP[i][j]+=max(DP[i-1][j-1],DP[i-1][j])。
#include<iostream> #include<cstring> using namespace std; const int MAX=110; #define max(a,b) a>b?a:b #define CLR(arr,val) memset(arr,val,sizeof(arr)) int n,maxt,DP[MAX][MAX]; int main() { cin>>n; CLR(DP,0); maxt=0; for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) { cin>>DP[i][j]; DP[i][j]+=max(DP[i-1][j-1],DP[i-1][j]); maxt=max(maxt,DP[i][j]); } cout<<maxt<<endl; return 0; }
例题2:NYOJ 17(最长单调递增子序列),设DP[i]为到i位置满足条件的最长递增子序列的长度,状态转移方程为:DP[i]=max(DP[i],DP[j]+1),其中(0<j<i)
#include<iostream> #include<string> #include<numeric> #include<algorithm> using namespace std; const int MAX=10010; string Str; int num,DP[MAX]; int main() { cin>>num; cin.get(); while(num--) { cin>>Str; fill(DP,DP+MAX,1); for(int i=0;i<Str.length();i++) for(int j=0;j<i;j++) if(Str[i]>Str[j]) DP[i]=max(DP[i],DP[j]+1); cout<<*max_element(DP,DP+Str.length())<<endl; } return 0; }
和上面这个题目一样的是NYOJ 79(拦截导弹),状态转移方程为DP[i]=max(DP[i],DP[j]+1)(只不过这个时候是Arr[i]<Arr[j]).
例题2:POJ 1088(滑雪),这是袁老师讲的第二个题目,今天自己做下,类似迷宫,只能朝四个方向移动。和上面的例题例题差不多,只是这个是求最长递减子序列的长度,且由一维的换为二维的,需要考虑四个方向。不多解释,应该容易看懂~
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int MAX=110; #define max(a,b) a>b?a:b #define CLR(arr,val) memset(arr,val,sizeof(arr)) int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0}; int num,row,col,map[MAX][MAX],DP[MAX][MAX]; bool Inside(int x,int y) { return x>=0&&x<row&&y>=0&&y<col; } int DFS(int Sx,int Sy) { if(DP[Sx][Sy]) return DP[Sx][Sy]; for(int i=0;i<4;i++) { int Newx=Sx+dx[i]; int Newy=Sy+dy[i]; if(map[Newx][Newy]<map[Sx][Sy]&&Inside(Newx,Newy)) DP[Sx][Sy]=max(DP[Sx][Sy],DFS(Newx,Newy)); } return DP[Sx][Sy]++; } int main() { scanf("%d",&num); while(num--) { CLR(DP,0); scanf("%d%d",&row,&col); for(int i=0;i<row;i++) for(int j=0;j<col;j++) scanf("%d",&map[i][j]); int maxt=0; for(int i=0;i<row;i++) for(int j=0;j<col;j++) maxt=max(maxt,DFS(i,j)); printf("%d\n",maxt); } return 0; }
例题3:NYOJ 30(Gone fishing)英语题目,今天开始翻译英语题目,我不懂语法,单词靠有道!!!呀~这个题目好长哦~~,不愿意翻译了,我翻译一句都要好久,而且还是乱翻译的,还是硬着头皮吧,只要翻译出来能看懂大概的意思就行.....