2017.12.24
简单的动态规划
1.数字三角形(算法引入)
题目描述:下图所示是一个数字三角形,其中三角形中的数值为正整数,现规定从最顶层往下走到最底层,每一步可沿左斜线向下或右斜线向下走。设三角形有n层,编程计算出从顶层到底层的一条路径,使得该路径上的和最大,输出最大值。(n<=100)
思路&&代码(搜索回溯):
最显而易见的思路,既然要求一条最短的路径,最简单的方法就是遍历所有的路径,找到一条最优的。时间复杂度是O(2n)以下是搜索代码。
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int map[101][101],n; 5 int count=0,ans=-20180101; 6 void search(int x,int y){ 7 count+=map[x][y]; 8 if(x==n){ 9 if(count>ans)ans=count; 10 } 11 else{ 12 search(x+1,y+1); 13 search(x+1,y); 14 } 15 count-=map[x][y]; 16 } 17 int main(){ 18 scanf("%d",&n); 19 int i,j; 20 for(i=1;i<=n;i++){ 21 for(j=1;j<=i;j++){ 22 scanf("%d",&map[i][j]); 23 } 24 } 25 search(1,1); 26 printf("%d\n",ans); 27 return 0; 28 }
思路&&代码(分治法):
对于任意一个点,我们可以把它的最大值和看成Max(sum[i+1][j],[i+1][j+1]),分解为两个规模更小的子问题。但是本质上和搜索没有区别,所以时间复杂度还是O(2n)。
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int n,map[101][101]; 5 int fenzhi(int x,int y){ 6 if(x==n)return map[x][y]; 7 int zi1,zi2,_max; 8 zi1=fenzhi(x+1,y); 9 zi2=fenzhi(x+1,y+1); 10 if(zi1<=zi2)_max=zi2; 11 else _max=zi1; 12 return _max+map[x][y]; 13 } 14 int main(){ 15 scanf("%d",&n); 16 int i,j; 17 for(i=1;i<=n;i++){ 18 for(j=1;j<=i;j++){ 19 scanf("%d",&map[i][j]); 20 } 21 } 22 printf("%d",fenzhi(1,1)); 23 return 0; 24 }
思路&&代码(记忆化):
比搜索要更优。我们注意到,不管是搜索还是分治,它们都是O(2n)的时间复杂度,是因为它们算了一些重复的东西。既然有重复,那我就把这些重复算的东西用一个数组保存起来,要用时就不用再去算一遍,只要调用了。所以把时间复杂度大大提升了,只有O(n2)了。
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int map[101][101],book[101][101]; 5 int n; 6 int max(int x,int y){ 7 if(y>x) 8 return y; 9 else 10 return x; 11 } 12 int search(int r,int c){ 13 int ans; 14 if (r==n) return map[r][c]; 15 if (book[r+1][c]==-1) 16 book[r+1][c]=search(r+1,c); 17 if (book[r+1][c+1]==-1) 18 book[r+1][c+1]=search(r+1,c+1); 19 ans=max(book[r+1][c],book[r+1][c+1])+book[r][c]; 20 return ans; 21 } 22 int main(){ 23 scanf("%d",&n); 24 int i,j; 25 for(i=1;i<=n;i++){ 26 for(j=1;j<=i;j++){ 27 scanf("%d",&map[i][j]); 28 book[i][j]=-1; 29 } 30 } 31 printf("%d",search(1,1)); 32 return 0; 33 }
思路&&代码(动态规划):
自底向上。第i层的任意一个点,其最大值是它自己加上它下一层的两个点的最大值之和。用i表示行,j表示列,状态转移方程如下。这样,时间复杂度也只有O(2n)。
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 int map[101][101],book[101][101]; 5 int max(int x,int y){ 6 if(x>y)return x; 7 else return y; 8 } 9 int main(){ 10 int n; 11 scanf("%d",&n); 12 int i,j; 13 for(i=1;i<=n;i++){ 14 for(j=1;j<=i;j++){ 15 scanf("%d",&map[i][j]); 16 } 17 } 18 for(j=1;j<=n;j++) 19 book[n][j]=map[n][j]; 20 for(i=n-1;i>=1;i--) 21 for(j=1;j<=i;j++) 22 book[i][j]=max(book[i+1][j+1],book[i+1][j])+map[i][j]; 23 printf("%d",book[1][1]); 24 return 0; 25 }
2.最长上升子序列
思路:
对于以第i个数为右端点的一个序列,他本身就是一个长度为1的上升子序列。这时,如果它的右边有比它数值更小的数,这时的最长上升子序列就是这个元素本身的长度1和以他前面的比他数值更小的这个元素为右端点的最长上升子序列的长度和。
状态转移方程:sum[i]=_Max(sum[i],sum[j]+1); (j<i,num[j]<num[i])
核心代码:
1 sum[1]=1; 2 for(i=2;i<=n;i++){ 3 sum[i]=1; 4 for(j=1;j<i;j++){ 5 if(num[j]<num[i]){ 6 sum[i]=_Max(sum[i],sum[j]+1); 7 } 8 } 9 } 10 for(i=1;i<=n;i++) 11 max=_Max(max,sum[i]);
状态:AC