当前位置:   article > 正文

C语言刷题之动态规划进阶(一)_c语言动态规划数据结构

c语言动态规划数据结构

目录

1.前言

2.过河卒

        1.题目

         2.初步分析

        3.代码实现

3.打家劫舍

        1.题目

        2.初步分析

        3.代码实现

4.最长上升子序列

        1.题目

        2.初步分析

        3.代码实现


1.前言

        前几期我们结束了动态规划的入门训练,相信对动态规划算法的使用和套路有了一定的了解。接下来我们将步入动态规划进阶训练,从一维dp数组逐步过渡到多维dp数组。老样子,本期依旧带来三道题目。上期传送门:C语言刷题之动态规划入门(三)

这三道题目分别是:过河卒,打家劫舍和最长上升子序列

2.过河卒

        1.题目

描述

棋盘上 A点有一个过河卒,需要走到目标 B点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,A 点 (0, 0)、B点(n,m),同样马的位置坐标是需要给出的。

现在要求你计算出卒从 A点能够到达 B点的路径的条数,假设马的位置(x,y)是固定不动的,并不是卒走一步马走一步。

注:马一次跳跃到达的点(x1,y1)和马原坐标(x,y)的关系是 |x1-x|+|y1-y|=3 ,且∣x1−x∣+∣y1−y∣=3,且x1!​=x,y1!=y 

数据范围:1≤n,m≤20 ,马的坐标0≤x,y≤20 ,1≤a,b,c,d≤1000 

输入描述:

仅一行,输入 n,m,x,y 四个正整数。分别表示B点坐标和马的坐标

输出描述:

输出路径总数


          2.初步分析

        题目要求求出A到B点卒的路径总数,由于是一个二维平面,于是我们的dp数组就是一个(n+1)*(m+1)的二维数组,数组每个元素代表到达当前点的路径总数。然后通过分析题目,初始化dp数组,找出状态转移方程,即可求解本题。

我的思路是:创建一个(n+1)*(m+1)的dp数组,由题目可得卒只能往下走和往右走,且不能走到马的控制点。因此,我们可以建立一个函数判断某一点是否是马的控制点,如果是,则将此点的路径置0,代表不能到达此点;如果不是,则通过dp[i][j]=dp[i-1][j]+dp[i][j-1]对dp数组进行赋值。主要代码如下:


         3.代码实现

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int Judge(int i, int j, int x, int y) //判断是否为马的控制点
  4. {
  5. if ((abs(i - x) + abs(j - y) == 3 && i != x && j != y) || (i == x && j == y))
  6. {
  7. return 1;
  8. }
  9. else
  10. return 0;
  11. }
  12. int main()
  13. {
  14. int n = 0, m = 0, x = 0, y = 0; //代表b点坐标和马的坐标
  15. scanf("%d %d %d %d", &n, &m, &x, &y);
  16. int** dp = (int**)malloc(sizeof(int*) * (n+1)); //建立dp数组(二维)
  17. for (int i = 0; i < n+1 ; i++)
  18. {
  19. dp[i] = (int*)malloc(sizeof(int) * (m+1));
  20. }
  21. dp[0][0] = 1; //初始化数组
  22. for (int i = 1; i < n + 1; i++) //初始化第一行第一列,只能往上或往下其中之一
  23. {
  24. if (Judge(i,0,x,y)==0) //不是控制点
  25. {
  26. dp[i][0] = dp[i-1][0];
  27. }
  28. else //是控制点
  29. {
  30. dp[i][0] = 0;
  31. }
  32. }
  33. for (int j = 1; j < m + 1; j++)
  34. {
  35. if (Judge(0, j, x, y) == 0)
  36. {
  37. dp[0][j] = dp[0][j-1];
  38. }
  39. else
  40. {
  41. dp[0][j] = 0;
  42. }
  43. }
  44. for (int i = 1; i < n + 1; i++) //遍历dp数组
  45. {
  46. for (int j = 1; j < m + 1; j++)
  47. {
  48. if (Judge(i, j, x, y) == 0)
  49. {
  50. dp[i][j] = dp[i][j - 1]+dp[i-1][j];
  51. }
  52. else
  53. {
  54. dp[i][j] = 0;
  55. }
  56. }
  57. }
  58. printf("%d", dp[n][m]);
  59. free(dp);
  60. return 0;
  61. }

注:当n和m超过20时,dp数组的数据类型需用long long,否则数据过大会超出

3.打家劫舍

        1.题目

描述

你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。

给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足1≤n≤2×100000 ,数组中每个值满足1≤nums[i]≤5000 

输入描述:

第一行输入一个正整数 n ,表示数组的长度。

第二行输入 n 个正整数,表示每个房间存有的现金。

输出描述:

输出最多的偷窃金额


        2.初步分析

        由于首尾相连且题目要求所偷的房间不能相连,则首尾不能都偷。于是可以分成两种情况,分别为包含首项不包含首项,如果包含首项,则最大值就为dp[n-2]或dp[n-3],如果包含首项,则最大值为dp[n-1]或dp[n-2],然后再求出两种情况最大值即可求出最多偷窃金额。

我的思路是:根据上述分析,建立两个dp数组,分别代表包含首项的数组和不包含首项的数组,然后依次初始化求最大值。其中,两个dp数组的状态转移方程都是:

dp[i]=dp[arr]+max(dp[i-2],dp[i-3]);

由于我们i是从3开始,因此,我们在进行遍历数组之前,需要根据dp数组种类对前三项进行初始化。最后输出两个dp数组的较大值即可。


         3.代码实现

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int max(int x, int y)
  4. {
  5. return x > y ? x : y;
  6. }
  7. int main()
  8. {
  9. int n = 0;
  10. scanf("%d", &n);
  11. int *arr=(int*)malloc(sizeof(int) * n);
  12. for (int i = 0; i < n; i++)
  13. {
  14. scanf("%d", &arr[i]);
  15. }
  16. if (n == 1) //只有一个数,则输出
  17. {
  18. printf("%d",arr[0]);
  19. }
  20. if (n == 2) //只有两个数,输出较大一个
  21. {
  22. printf("%d", max(arr[0], arr[1]));
  23. }
  24. int* dp1 = (int*)malloc(sizeof(int) * n); //包括首项的
  25. int* dp2 = (int*)malloc(sizeof(int) * n); //不包括首项
  26. dp1[0] = arr[0]; //赋初值
  27. dp1[1] = arr[1];
  28. dp1[2] = dp1[0] + arr[2];
  29. for (int i = 3; i < n; i++)
  30. {
  31. dp1[i] = max(arr[i]+dp1[i-3], arr[i] + dp1[i - 2]);
  32. }
  33. int max1 = max(dp1[n - 2], dp1[n - 3]); //包括首项,则尾项不包括,此时最大值为前两项之一
  34. dp1[0] = 0;
  35. dp1[1] = arr[1];
  36. dp1[2] = arr[2];
  37. for (int i = 3; i < n; i++)
  38. {
  39. dp1[i] = max(arr[i] + dp1[i - 3], arr[i] + dp1[i - 2]);
  40. }
  41. int max2 = max(dp1[n-1], dp1[n - 2]);//不包括首项,此时最大值为尾项或前一项
  42. printf("%d", max(max1, max2)); //取两种情况最大值
  43. free(dp1);
  44. free(dp2);
  45. return 0;
  46. }

4.最长上升子序列

        1.题目

描述

给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。
例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。
我们定义一个序列是 严格上升 的,当且仅当该序列不存在两个下标 i 和 j 满足 i<j且arr[i]>=arr[j]

数据范围:0≤n≤1000 , |arr[i]|≤10^9 
要求:时间复杂度 O(n^2),空间复杂度 O(n)

输入描述:

第一行输入一个正整数 n ,表示数组的长度
第二行输入 n 个整数表示数组的每个元素

输出描述:

输出最长严格上升子序列的长度


        2.初步分析

        本题的总体思路其实和上期总结的求连续最大子数组的思路一样,详情请看:C语言刷题之动态规划入门(三)

我的思路是:建立一个dp数组,数组的每个元素代表包含当前元素的最长严格上升子数组,然后再遍历前i个元素,找出小于当前项的元素,我们可以很轻松地得出状态转移方程:

dp[i]=max(dp[j]+1,dp[i])

然后再遍历dp数组找出最大值即可得出答案。


         3.代码实现

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. int Max(int x, int y)
  4. {
  5. return x > y ? x : y;
  6. }
  7. int main()
  8. {
  9. int n = 0;
  10. scanf("%d", &n);
  11. int *arr =(int *)malloc(n * sizeof(int));
  12. for (int i = 0; i < n; i++)
  13. {
  14. scanf("%d", &arr[i]);
  15. }
  16. int* dp = (int*)malloc(n * sizeof(int)); //存储包含当前项的最长长度的dp数组
  17. for (int i = 0; i < n; i++) //赋初值,默认为1
  18. {
  19. dp[i] = 1;
  20. }
  21. for (int i = 0; i < n; i++)
  22. {
  23. for (int j = 0; j < i; j++) //查找前i项是否存在比arr[i]小的元素
  24. {
  25. if (arr[i] > arr[j]) //找到了
  26. {
  27. dp[i] = Max(dp[j] + 1,dp[i]); //dp[i]为其中最长的那个
  28. }
  29. }
  30. }
  31. int max = dp[0];
  32. for (int i = 1; i < n; i++)
  33. {
  34. if (dp[i] > max)
  35. {
  36. max = dp[i]; //找出dp数组的最大值,即为所求
  37. }
  38. }
  39. printf("%d", max);
  40. return 0;
  41. }

 以上,就是动态规划进阶刷题(一)的全部内容。

制作不易,能否点个赞再走呢qwq

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

闽ICP备14008679号