当前位置:   article > 正文

一篇文章带你快速入门DP动态规划——C++_左上到右下多少种方案dp c++

左上到右下多少种方案dp c++

前言:

博主是一名大一编程小白,因为马上要参加蓝桥杯,所以最近一直在学习动态规划,接下来我将分享我遇到的经典例题和我能力所及的最清晰的代码,并且会逐渐丰富文章内容,分享思路,希望和大家共同进步!

因为内容较多,建议收藏慢慢研究。

学习笔记:

动态规划题目特点
1.计数
    —有多少种方式走到右下角
    —有多少种方法选出k个数使得和为sum
2.求最大最小值
    —从左上角走到右下角路径的最大数字和
    —最长上升子序列长度
3.求存在性
    —取石子游戏,先手是否必胜
    —能不能选出k个数使得和为sum 


动态规划组成部分一:确定状态
    最后一步(最优策略的最后一步)
    化成子问题 
动态规划组成部分二:转移方程
动态规划组成部分三:初始条件和边界情况 
    用转移方程算不出来,需要手工定义
动态规划组成部分四:计算顺序
    利用之前的计算结果    
    一维从小到大(大部分)
    二维从上到下,从左到右(大部分)

常见动态规划类型
    坐标型动态规划
    序列型动态规划
    划分型动态规划
    区间型动态规划
    背包型动态规划
    最长序列型动态规划
    博弈型动态规划
    综合性动态规划

例一    Unique Paths

题目描述:

给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步,问有多少种不同的方式走到右下角?

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. void dp(int m,int n)
  4. {
  5. int f[m][n];
  6. memset(f,0,sizeof(f));
  7. int i,j;
  8. f[0][0]=1;
  9. for(j=0;j<n;j++)
  10. f[0][j]=1;
  11. for(i=0;i<m;i++)
  12. f[i][0]=1;
  13. for(i=1;i<m;i++)
  14. {
  15. for(j=1;j<n;j++)
  16. {
  17. f[i][j]=f[i-1][j]+f[i][j-1];
  18. }
  19. }
  20. printf("%d\n",f[m-1][n-1]);
  21. }
  22. int main()
  23. {
  24. int m,n;
  25. scanf("%d%d",&m,&n);
  26. dp(m,n);
  27. return 0;
  28. }

运行结果:

例二    激光样式

题目描述:

x星球的盛大节日为增加气氛,用30台激光器一字排开,向太空中打出光柱。
安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!
国王很想知道,在目前这种bug存在的情况下,一共能打出多少种激光效果?

显然,如果只有3台机器,一共可以成5种样式,即:
全都关上(sorry, 此时无声胜有声,这也算一种)
开一台,共3种
开两台,只1种

30台就不好算了,国王只好请你帮忙了。

要求提交一个整数,表示30台激光器能形成的样式种数。

注意,只提交一个整数,不要填写任何多余的内容。

网上摘抄代码如下:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. /* run this program using the console pauser or add your own getch, system("pause") or input loop */
  6. using namespace std;
  7. bool get(int x){
  8. if(x&(x<<1))return false;
  9. else return true;
  10. }
  11. int main(int argc, char *argv[]) {
  12. int ans=0;
  13. for(int i=0;i<1<<30;i++){
  14. if(get(i)){
  15. ans++;
  16. }
  17. }
  18. cout<<ans<<endl;
  19. return 0;
  20. }

网上摘抄代码运行结果:

我的思路:

我的代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int arr[31];
  6. int i;
  7. arr[1]=2;
  8. arr[2]=3;
  9. for(i=3;i<=30;i++)
  10. arr[i]=arr[i-1]+arr[i-2];
  11. printf("%d\n",arr[30]);
  12. }

我的代码运行结果:

疑问:

我直接找到了转移方程和出口,此代码非常简单,有点小学生找规律的味道,不知道如果在蓝桥杯这样写对不对。请大佬指正,感谢!

例三    Unique Paths II

题目描述:

给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一次可以向下或者向右走一步,网格中有些地方有障碍,机器人不能通过障碍。问:有多少种不同的方式走到右下角?

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int m,n;
  6. scanf("%d %d",&m,&n); //输入行列数m,n
  7. int f[m][n],dp[m][n];
  8. int i,j,k;
  9. for(i=0;i<m;i++) //输入m*n网格,1为有障碍,0为无障碍
  10. {
  11. for(j=0;j<n;j++)
  12. {
  13. scanf("%d",&f[i][j]);
  14. }
  15. }
  16. for(i=0;i<m;i++)
  17. {
  18. for(j=0;j<n;j++)
  19. {
  20. if(f[i][j]==1)
  21. dp[i][j]=0;
  22. else
  23. {
  24. if(i==0&&j==0)
  25. dp[i][j]=1;
  26. else
  27. {
  28. dp[i][j]=0;
  29. if(i-1>=0)
  30. dp[i][j]+=dp[i-1][j];
  31. if(j-1>=0)
  32. dp[i][j]+=dp[i][j-1];
  33. }
  34. }
  35. }
  36. }
  37. printf("%d\n",dp[m-1][n-1]);
  38. return 0;
  39. }

运行结果:

疑惑:

好像运行时间有点长???

例四    Jump Game

题目描述:

有n块石头分别在x轴的0,1,2,…,n-1位置上。一只青蛙在石头0,想跳到石头n-1上。如果一只青蛙在第i块石头上,它最多可以向右跳ai。问,青蛙能否跳到n-1?

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int i,j,n;
  6. scanf("%d",&n); //输入石头的数量
  7. int a[n],f[n];
  8. for(i=0;i<n;i++) //依次输入在第i块石头上,最多可以向右跳的距离
  9. scanf("%d",&a[i]);
  10. f[0]=1;
  11. for(j=1;j<n;j++)
  12. {
  13. f[j]=0;
  14. for(i=0;i<j;i++)
  15. {
  16. if(f[i]==1&&i+a[i]>=j)
  17. {
  18. f[j]=1;
  19. break;
  20. }
  21. }
  22. }
  23. if(f[n-1]==1)
  24. printf("Yes\n");
  25. else
  26. printf("No\n");
  27. }

运行结果:

例五    Paint House

题目描述:

有一排N栋房子,每栋房子要漆成3种颜色中的一种:红、蓝、绿。任何两栋相邻的房子不能漆成同样的颜色第i栋房子染成红色、蓝色、绿色的花费分别是cost[i][0]、cost[i][1]、cost[i][2]。问:最少花多少钱漆这个房子?

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int MIN(int m,int n,int t)
  4. {
  5. if(m>n)
  6. m=n;
  7. return m>t?t:m;
  8. }
  9. int main()
  10. {
  11. int N,i,j;
  12. scanf("%d",&N); //输入房子数量
  13. int cost[N][3],f[N+1][3]; //i表示前i栋 i表示前i栋 i表示前i栋 i表示前i栋
  14. for(i=0;i<N;i++) //分别输入每栋房子染成红色、蓝色、绿色的花费
  15. for(j=0;j<3;j++)
  16. {
  17. scanf("%d",&cost[i][j]);
  18. }
  19. f[0][0]=f[0][1]=f[0][2]=0;
  20. for(i=1;i<=N;i++)
  21. {
  22. f[i][0]=min(f[i-1][1]+cost[i-1][0],f[i-1][2]+cost[i-1][0]);
  23. f[i][1]=min(f[i-1][0]+cost[i-1][1],f[i-1][2]+cost[i-1][1]);
  24. f[i][2]=min(f[i-1][0]+cost[i-1][2],f[i-1][1]+cost[i-1][2]);
  25. }
  26. printf("%d\n",MIN(f[N][0],f[N][1],f[N][2]));
  27. return 0;
  28. }

运行结果:

疑问:

运行速度一如既往得慢,希望大佬提出改进意见,感谢!

例六    Minimum Path Sum

题目描述:

给定m行n列的网格,每个格子(i,j)里都有一个非负数A[i][j],求一个从左上角(0,0)到右下角的路径,每一步只能向下或者向右走一步,使得路径上的格子里的数字之和最小,输出最小数字和。

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int i,j,m,n;
  6. scanf("%d %d",&m,&n);
  7. int A[m][n],f[m][n];
  8. for(i=0;i<m;i++)
  9. for(j=0;j<n;j++)
  10. scanf("%d",&A[i][j]);
  11. f[0][0]=A[0][0];
  12. for(i=1;i<m;i++)
  13. f[i][0]=f[i-1][0]+A[i][0];
  14. for(j=1;j<n;j++)
  15. f[0][j]=f[0][j-1]+A[0][j];
  16. for(i=1;i<m;i++)
  17. for(j=1;j<n;j++)
  18. f[i][j]=min(f[i-1][j],f[i][j-1])+A[i][j];
  19. printf("%d\n",f[m-1][n-1]);
  20. return 0;
  21. }

运行结果:

优化代码:

滚动数组滚动数组滚动数组

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int i,j,m,n;
  6. scanf("%d%d",&m,&n);
  7. int A[m][n],dp[2][n];
  8. for(i=0;i<m;i++)
  9. for(j=0;j<n;j++)
  10. scanf("%d",&A[i][j]);
  11. for(i=0;i<m;i++)
  12. {
  13. if(i==0)
  14. {
  15. dp[0][0]=A[0][0];
  16. for(j=1;j<n;j++)
  17. {
  18. dp[0][j]=dp[0][j-1]+A[0][j];
  19. }
  20. }
  21. else
  22. {
  23. if(i%2==1)
  24. {
  25. dp[1][0]=dp[0][0]+A[i][0];
  26. for(j=1;j<n;j++)
  27. {
  28. dp[1][j]=min(dp[0][j],dp[1][j-1])+A[i][j];
  29. }
  30. }
  31. else
  32. {
  33. dp[0][0]=dp[1][0]+A[i][0];
  34. for(j=1;j<n;j++)
  35. {
  36. dp[0][j]=min(dp[1][j],dp[0][j-1])+A[i][j];
  37. }
  38. }
  39. }
  40. }
  41. if(i%2==1)
  42. {
  43. printf("%d\n",dp[0][n-1]);
  44. }
  45. else
  46. {
  47. printf("%d\n",dp[1][n-1]);
  48. }
  49. return 0;
  50. }

 

 

例七    数字三角形问题

题目描述:

给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法,计算出从三角形的顶至底的一条路径,使该路径经过的数字总和最大。

对于给定的由n行数字组成的数字三角形,计算从三角形的顶至底的路径经过的数字和的最大值。

代码如下: 

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int n,i,j;
  6. scanf("%d",&n);
  7. int a[n][n];
  8. memset(a,0,sizeof(a));
  9. for(i=0;i<n;i++)
  10. for(j=0;j<=i;j++)
  11. scanf("%d",&a[i][j]);
  12. int dp[n][n];
  13. memset(dp,0,sizeof(dp));
  14. for(j=0;j<n;j++)
  15. dp[n-1][j]=a[n-1][j];
  16. for(i=n-2;i>=0;i--)
  17. {
  18. for(j=0;j<=i;j++)
  19. {
  20. dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
  21. }
  22. }
  23. printf("%d\n",dp[0][0]);
  24. return 0;
  25. }
  1. #include<iostream>
  2. #include<algorithm>
  3. #define MAX 101
  4. using namespace std;
  5. int n;
  6. int D[MAX][MAX];
  7. int maxsum[MAX][MAX];
  8. int MaxSum(int i,int j)
  9. {
  10. if(maxsum[i][j]!=-1)
  11. return maxsum[i][j];
  12. if(i==n)
  13. maxsum[i][j]=D[i][j];
  14. else
  15. {
  16. int x=MaxSum(i+1,j);
  17. int y=MaxSum(i+1,j+1);
  18. maxsum[i][j]=max(x,y)+D[i][j];
  19. }
  20. return maxsum[i][j];
  21. }
  22. int main()
  23. {
  24. cin>>n;
  25. int i,j;
  26. for(i=1;i<=n;++i)
  27. for(j=1;j<=i;++j)
  28. {
  29. cin>>D[i][j];
  30. maxsum[i][j]=-1;
  31. }
  32. cout<<MaxSum(1,1)<<endl;
  33. return 0;
  34. }

另附递归代码:

  1. #include<bits/stdc++.h>
  2. #define MAX 101
  3. using namespace std;
  4. int n;
  5. int a[MAX][MAX];
  6. int maxsum[MAX][MAX]; //记录避免重复运算
  7. int Maxsum(int i,int j)
  8. {
  9. if(maxsum[i][j]!=-1)
  10. return maxsum[i][j];
  11. if(i==n)
  12. maxsum[i][j]=a[i][j];
  13. else
  14. maxsum[i][j]=max(Maxsum(i+1,j),Maxsum(i+1,j+1))+a[i][j];
  15. return maxsum[i][j];
  16. }
  17. int main()
  18. {
  19. int i,j;
  20. scanf("%d",&n); //三角形行数
  21. for(i=1;i<=n;++i) //从第一行开始输入
  22. for(j=1;j<=i;++j)
  23. {
  24. scanf("%d",&a[i][j]);
  25. maxsum[i][j]=-1;
  26. }
  27. printf("%d\n",Maxsum(1,1));
  28. return 0;
  29. }

空间优化代码:

  1. #include<iostream>
  2. #include<algorithm>
  3. #define MAX 101
  4. using namespace std;
  5. int n;
  6. int *maxsum;
  7. int D[MAX][MAX];
  8. int main()
  9. {
  10. cin>>n;
  11. int i,j;
  12. for(i=1;i<=n;++i)
  13. for(j=1;j<=i;++j)
  14. cin>>D[i][j];
  15. maxsum=D[n];
  16. for(i=n-1;i>=1;--i)
  17. for(j=1;j<=i;++j)
  18. maxsum[j]=max(maxsum[j],maxsum[j+1])+D[i][j];
  19. cout<<maxsum[1]<<endl;
  20. return 0;
  21. }

运行结果:

例八    K好数

题目描述:

如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。
例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。

代码如下:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define NUM 1000000007;
  4. int main()
  5. {
  6. int K,L;
  7. int i,j,x,count=0;
  8. scanf("%d%d",&K,&L);
  9. int arr[L+1][K];
  10. memset(arr,0,sizeof(arr));
  11. for(i=0;i<K;++i)
  12. arr[1][i]=1;
  13. for(i=2;i<=L;++i)
  14. {
  15. for(j=0;j<K;++j)
  16. {
  17. for(x=0;x<K;++x)
  18. {
  19. if(x!=j-1&&x!=j+1)
  20. {
  21. arr[i][j]+=arr[i-1][x];
  22. arr[i][j]%=NUM;
  23. }
  24. }
  25. }
  26. }
  27. for(i=1;i<K;++i)
  28. {
  29. count+=arr[L][i];
  30. count%=NUM;
  31. }
  32. printf("%d\n",count);
  33. return 0;
  34. }

运行结果:

例九    最长上升子序列

最长上升子序列(动态规划)——C++

例十    最长公共子序列

题目描述:

给出两个字符串,求出这样一个最长的公共子序列的长度:子序列中的每个字符都能在两个原字符串中找到,而且每个字符的先后顺序和原字符串中的先后顺序一致。

图解:

代码如下:

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. char str1[1000];
  5. char str2[1000];
  6. int maxlength[1000][1000];
  7. int main()
  8. {
  9. cin>>str1>>str2;
  10. int length1=strlen(str1);
  11. int length2=strlen(str2);
  12. int i,j;
  13. //maxlength[i][j]表示str1左边i个字符形成的子字符串和str2左边的j个字符形成的子字符串的最长公共子序列的长度
  14. for(i=0;i<=length1;++i)
  15. maxlength[i][0]=0;
  16. for(j=0;j<=length2;++j)
  17. maxlength[0][j]=0;
  18. for(i=1;i<=length1;++i)
  19. {
  20. for(j=1;j<=length2;++j)
  21. {
  22. if(str1[i-1]==str2[j-1])
  23. maxlength[i][j]=maxlength[i-1][j-1]+1;
  24. else
  25. maxlength[i][j]=max(maxlength[i-1][j],maxlength[i][j-1]);
  26. }
  27. }
  28. cout<<maxlength[length1][length2]<<endl;
  29. return 0;
  30. }

运行结果:

例十一    最佳加法表达式

最佳加法表达式——动态规划详解——C++

例十二    神奇的口袋

题目描述:

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是 a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

输入:

输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出 a1,a2……an的值。

输出:

输出不同的选择物品的方式的数目。

代码如下:

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. int N;
  5. int a[20+1];
  6. int Ways[40+1][20+1]; //Ways[i][j]表示从前j中物品中凑出体积i的方法数
  7. int main()
  8. {
  9. cin>>N;
  10. memset(Ways,0,sizeof(Ways));
  11. for(int i=1;i<=N;++i) //下标从1开始
  12. {
  13. cin>>a[i];
  14. Ways[0][i]=1;
  15. }
  16. Ways[0][0]=1;
  17. for(int m=1;m<=40;++m)
  18. {
  19. for(int k=1;k<=N;++k)
  20. {
  21. Ways[m][k]=Ways[m][k-1];
  22. if(m-a[k]>=0)
  23. Ways[m][k]+=Ways[m-a[k]][k-1];
  24. }
  25. }
  26. cout<<Ways[40][N]<<endl;
  27. return 0;
  28. }
  29. /*
  30. #include<iostream>
  31. using namespace std;
  32. int a[21];
  33. int Ways(int m,int k)
  34. {
  35. if(m==0)
  36. return 1;
  37. if(k<=0)
  38. return 0;
  39. return Ways(m,k-1)+Ways(m-a[k],k-1);
  40. }
  41. int main()
  42. {
  43. int n;
  44. cin>>n;
  45. for(int i=1;i<=n;++i)
  46. cin>>a[i];
  47. cout<<Ways(40,n)<<endl;
  48. return 0;
  49. }
  50. */

运行结果:

例十三    0-1背包问题  

0-1背包问题—动态规划+滚动数组—C++超详解

例十四    多重背包问题

https://blog.csdn.net/weixin_45953673/article/details/104932290

例十五    完全背包问题

完全背包问题——动态规划——C++详解

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/961617
推荐阅读
相关标签
  

闽ICP备14008679号