文章目录

  • 一、动态规划的递归和递推写法
    • 1、递归写法
    • 2、递推写法
    • 3、分治、贪心与动态规划
  • 二、最大连续子序列和
  • 三、最长不下降子序列(LIS)
  • 四、最长公共子序列(LCS)
  • 五、最长回文子串
  • 六、背包问题
    • 1、0-1背包问题
    • 2、完全背包问题

一、动态规划的递归和递推写法

动态规划: 将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题的时候就可以不用重复计算。

1、递归写法

以斐波拉契数列为例:

int F(int n){if(n == 0 || n == 1)return 1;elsereturn F(n-1)+F(n-2);
}

事实上,这个递归过程中会涉及很多的重复计算。比如说F(5) = F(4)+F(3),接下来F(4) = F(3)+F(2)。这个时候,如果不采取措施,那么F(3)就会计算两次。为了避免重复计算,可以开一个一维数组dp,用来保存已经计算过的结果。并且dp[n]=-1表示F(n)当前还没有计算过:

int F(int n){if(n == 0 || n == 1)return 1;if(dp[n] != -1)return dp[n];//已经被计算过else {dp[n] = F(n-1) + F(n-2);return dp[n];}
}

如果一个问题可以被分解成为多个子问题,且这些子问题会重复出现,那么就称这个问题拥有重叠子问题。动态规划通过记录重叠子问题的解,使得下次碰到相同的子问题时可以直接使用之前记录的结果,依次避免大量的重复计算。因此,一个问题必须要有重叠子问题,才能用动态规划去解决。

2、递推写法


以如图的数塔问题为例,现在从最顶层走到最底层,每次只能走向下一层连接数字中的一个,请问最后将路径上的数字相加之后得到的数值最大是多少?

用一个二维数组f,其中f[ i ] [ j ] 表示第i层的第j个数字,比如f[1][1]=13。首先,从第一层的13开始,按照13->11->7的路径到达7后,之后要枚举7到达底层的最大和。从13->8->7到达7之后,也要枚举7到达底层的最大和。因此,不妨设一个二维数组dp,dp[3][2]表示7到达底层的最大和

从顶部开始求解,那么其实dp[1][1] = f[1][1]+max{dp[2][1],dp[2][2]}

因此,可以得到递推公式:dp[i][j] = f[i][j]+max{dp[i+1][j],dp[i+1][j+1]}。而dp[n][j] = f[n][j]是递推的边界。而递推的方法总是从这些边界开始,然后不断向上求出每一个dp的值,最后得到想要的答案。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1000;
int f[maxn][maxn],dp[maxn][maxn];int main(){int n;scanf("%d",&n);for(int i = 1 ; i <= n ; i++){for(int j = 1 ; j <= i ; j++){scanf("%d",&f[i][j]);}}//边界for(int j = 1 ; j <= n ; j++)dp[n][j] = f[n][j];//从第n-1层开始,不断向上计算for(int i = n-1 ; i >= 1 ; i--){for(int j = 1 ; j <= i ; j++){dp[i][j] = f[i][j] + max(dp[i+1][j],dp[i+1][j+1]);}}printf("%d",dp[1][1]);return 0;
}


显然,也可以用递归来实现。区别在于:递推的计算方式是自底而上,而递归是自顶向下。

如果一个问题的最优解可以由其子问题的最优解有效构造出来,那么称这个问题拥有最优子结构。最优子结构保证动态规划中元有问题的最优解可以由子问题的最优解推导出来。因此,一个问题必须要有最优子结构才能够用动态规划去解决问题。

综上所述,一个问题必须要有重叠子问题和最优子结构,才能够用动态规划去解决。

3、分治、贪心与动态规划

分治与动态规划:都是将问题分解为子问题,但是分治并没有重叠子问题。
贪心与动态规划:贪心并不要求等待子问题全部求解完毕之后再选择使用哪一个,而是用一次策略去选择一个子问题去求解,没有被选择到的子问题就被抛弃了。

二、最大连续子序列和

题目:给定一个数字序列A1,A2,…,An,求i,j,使得Ai+…+Aj最大,输出这个最大和

样例:-2 11 -4 13 -5 -2 显然11+(-4)+13=20位最大和

思路:

  1. 令状态dp[i]表示以A[i]最为结尾的最大子序列的和(A[i]必须是连续子序列的末尾)
dp[0]=-2
dp[1]=11
dp[2]=7(11-4)
sp[3]=20(11-4+13)
dp[4]=15(11-4+13-5)
dp[5]=13(11-4+13-5-2)
  1. 因为dp[i]要求必须以A[i]为结尾,因此就有两种情况:

    (1)这个最大和的连续序列只有一个元素:A[i]
    (2)这个最大和的连续序列有多个元素,最大和就是dp[i-1]+A[i]
    因此,dp[i]=max{A[i],dp[i-1]+A[i]}

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10010;
int A[maxn],dp[maxn];
int main(){int n;scanf("%d",&n);for(int i = 0 ; i < n ; i++)scanf("%d",&A[i]);dp[0] = A[0];for(int i = 1 ; i < n ; i++){dp[i] = max(A[i],dp[i-1]+A[i]);}int k = 0;for(int i = 1 ; i < n ; i++){if(dp[i]>dp[k]){k=i;}}printf("%d",dp[k]);return 0;
}

三、最长不下降子序列(LIS)

题目:在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降的(非递减)

例如:现有序列A={1,2,3,-1,-2,7,9},下标从1开始,它的最长不下降子序列是{1,2,3,7,9},长度为5

令dp[i]表示以A[i]结尾的最长不下降子序列的长度,这样对于A[i]来说就会有两种情况:

  1. 如果A[i]之前元素A[j],使得A[j]<=A[i]且dp[j]+1>dp[i],那么就让A[I]跟在A[j]后面形成一条更长的子序列,即令dp[i] = dp[j]+1
  2. 如果A[i]之前的元素逗比A[i]大,那么A[i]只能自己形成一条LIS,长度为1

比如说有一个序列A={1,5,-1,3},下标从1开始,现在如何直到以A[4]结尾的最长子序列呢:依次判断A[1]、A[2]、A[3],经判断,A[4]可以跟在A[1]或者A[3]后面形成LIS,长度为2。又如序列{1,2,3,-1},这里的A[4]并不能跟在任何一个元素后面,因此LIS就为-1一个元素,长度为1

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 100;
int A[N],dp[N];
int main(){int n;scanf("%d",&n);for(int i = 1 ; i <= n ; i++)scanf("%d",&A[i]);int ans = -1;//标记最大的子序列长度for(int i = 1 ; i <= n ; i++){dp[i] = 1;//初始条件是每个元素结尾的最长子序列中只有自己for(int j = 1 ; j < i ; j++){if(A[i] >= A[j] && dp[j]+1 > dp[i])dp[i] = dp[j] + 1;}ans = max(ans , dp[i]);}printf("%d",ans);return 0;
}

四、最长公共子序列(LCS)

题目:给定两个字符串或者数字序列A,B,求一个字符串,使得这个字符串是A和B的最长公共部分(子序列可以不连续

如:字符串"sadstory"和"adminsorry"的最长公共子序列为"adsory"

思路:令dp[i][j]表示字符串A的i号位和字符串B的j号位之间的LCS长度,下标从1开始,如dp[4][5]表示"sads"和“admin”的LCS长度,那么可能就会有两种情况:

  1. 若A[i]==B[j],那么字符串A和字符串B之间的公共子序列长度增加一位,即dp[i][j] = dp[i-1][j-1]+1,比如说dp[4][6]表示"sads"和"admins"的LCS长度,而A[4]==B[6],那么dp[4][6]=dp[3][5]+1,即为3
  2. 若A[i]!=B[j],则字符串A的i号位与字符串B的j号位之间的LCS无法延长,那么dp[i][j]将会继承dp[i-1][j]和dp[i][j-1]之间的较大值。比如dp[3][3]为"sad"和"adm"的LCS长度,A[3]不等于B[3],那么继承"sa"、“adm"的LCS或者"sad”、“ad"的LCS的较大值,即"sad”、“ad”,为2

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100;
char A[N],B[N];
int dp[N][N];
int main(){int n;gets(A+1);//从下标为1开始读gets(B+1);int lenA = strlen(A+1);int lenB = strlen(B+1);for(int i = 0 ; i <= lenA ; i++)dp[i][0] = 0;for(int j = 0 ; j <= lenB ; j++)dp[0][j] = 0;for(int i = 1 ; i <= lenA ; i++){for(int j = 1 ; j <= lenB ; j++){if(A[i] == B[j])dp[i][j] = dp[i-1][j-1]+1;elsedp[i][j] = max(dp[i-1][j],dp[i][j-1]);}}printf("%d",dp[lenA][lenB]);return 0;
}

五、最长回文子串

题目:给出一个字符串S,求出S的最长回文子串的长度
例如:字符串“PATZJUJZTACCBCC”的最长回文子串为"ATZJUJZTA",长度为9

思路:令dp[i][j]表示S[i]置S[j]的子串是否是回文子串,是则为1,不是则为0。这样就会有两种情况:

  1. 若S[i] = S[j],那么只要S[i+1]和S[j-1]是回文子串,那么S[i]到S[j]之间就为回文字串
  2. 若S[i]!=S[j],那么S[i]到S[j]一定不是回文子串

代码:

#include<cstdio>
#include<cstring>
const int maxn = 1010;
char S[maxn];
int dp[maxn][maxn];
int main(){gets(S);int len = strlen(S);int ans = 1;memset(dp,0,sizeof(dp));for(int i = 0 ; i < len ; i++){dp[i][i] = 1;if(i < len-1){if(S[i] == S[i+1]){dp[i][i+1] = 1;ans = 2;}}}for(int L = 3 ; L <= len ; L ++){//枚举子串的长度for(int i = 0 ; i+L-1 < len ; i++){int j = i+L-1;if(S[i]==S[j]&&dp[i+1][j-1]==1){dp[i][j]=1;ans = L;}}}printf("%d\n",ans);return 0;
}

六、背包问题

1、0-1背包问题

题目:有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内的总价值最大。其中每件物品只有一件

样例:

5  8//n=5 V=8
3 5 1 2 2 //w[i]
4 5 2 1 3 //c[i]

思路:令dp[i][v]表示前i件物品恰好装入容量为v的背包中所得的最大价值。对于每件物品:

  1. 不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中的最大价值,即dp[i-1][v]
  2. 放第i件物品,那么问题转换为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也就是dp[i-1][v-w[i]]+c[i]

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100;
const int maxv = 1000;
int w[maxn],c[maxn],dp[maxv];
int main(){int n,V;scanf("%d%d",&n,&V);for(int i = 1; i <= n ; i++)scanf("%d",&w[i]);for(int i = 1; i <= n ; i++)scanf("%d",&c[i]);for(int v = 0 ; v <= V ; v++)dp[v] = 0;for(int i = 1 ; i <= n ; i++){for(int v = V ; v >= w[i] ; v--){dp[v] = max(dp[v],dp[v-w[i]]+c[i]);}}int max = 0 ;for(int v = 0 ; v <= V ; v++){if(dp[v]>max){max = dp[v];}}printf("%d\n",max);return 0;
}

2、完全背包问题

和0-1背包问题相区别的就是,每种物品都有无穷件

那么对于每件物品:

  1. 不放第i件物品,那么dp[i][v] = dp[i-1][v]
  2. 放第i件物品,那么要转移到dp[i][v-w[i]]

即:dp[i][v] = max(dp[i-1][v],dp[i][v-w[i]]+c[i])

算法15——动态规划专题相关推荐

  1. openjudge-NOI 2.6基本算法之动态规划 专题题解目录

    1.1759 最长上升子序列 2.1768 最大子矩阵 3.1775 采药 4.1808 公共子序列 5.1944 吃糖果 6.1996 登山 7.2000 最长公共子上升序列 8.2718 移动路线 ...

  2. 【2】python算法练习--动态规划专题(1)

    区间dp模板题:合并石子 拿到这道题,根据题目所说,需要我们寻找最小合并的数值,很容易就联想到dp,去寻找子问题,化整为零.因为如果直接暴力的话,就是N的阶乘的时间复杂度,肯定没有办法在规定时间内解决 ...

  3. 杭电ACM-LCY算法进阶培训班-专题训练15

    杭电ACM-LCY算法进阶培训班-专题训练(03-07-11-15) 1012 最短路 #pragma GCC optimize(2) #pragma GCC optimize(3,"Ofa ...

  4. 算法训练Day44 动态规划专题- 背包问题 | 完全背包基础知识;LeetCode518. 零钱兑换(装满背包有多少种方法,组合数);377.组合总和IV(装满背包有多少种方法,排列数)

    前言: 算法训练系列是做<代码随想录>一刷,个人的学习笔记和详细的解题思路,总共会有60篇博客来记录,计划用60天的时间刷完.  内容包括了面试常见的10类题目,分别是:数组,链表,哈希表 ...

  5. NOI入门级:算法之动态规划

    糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322 糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322_哔哩哔哩_bilibili 程序员面试再也不怕动态规划了,看动画,学 ...

  6. 杭电ACM-LCY算法进阶培训班-专题训练(矩阵快速幂)

    杭电ACM-LCY算法进阶培训班-专题训练(矩阵快速幂)[模板] 传送门 杭电ACM-LCY算法进阶培训班-专题训练(矩阵快速幂)[模板] 矩阵快速幂模板 Count Problem Descript ...

  7. 算法:动态规划窃贼问题C语言实现

    算法:动态规划窃贼问题C语言实现 目录 算法:动态规划窃贼问题C语言实现 第一章 问题描述 1.1问题描述 第二章 算法思想及算法设计分析 2.1算法思想 2.2设计算法 2.3算法分析 2.4填表结 ...

  8. 【算法】动态规划 ① ( 动态规划简介 | 自底向上的动态规划示例 | 自顶向下的动态规划示例 )

    文章目录 一.动态规划简介 二.自底向上的动态规划示例 1.原理分析 2.算法设计 3.代码示例 三.自顶向下的动态规划示例 1.算法设计 2.代码示例 一.动态规划简介 动态规划 , 英文名称 Dy ...

  9. Bellman-Ford 算法 和 动态规划

    Floyd算法: 状态: d[k][i][j]定义:"只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度." 动态转移方程: d[k][i][j]=min(d[k ...

最新文章

  1. java dictionary 实例化_Java Dictionary put()用法及代码示例
  2. 区块链BaaS云服务(14)华大BGI区块链“概论“
  3. ajax empty,jQuery empty仅在AJAX调用后的第二次单击时起作用
  4. Django从理论到实战(part42)--QueryDict对象
  5. 张娟娟(为奥运冠军名字作诗)
  6. 项目上线最后工作——布署环境
  7. background 覆盖 内容_web开发:利用background制作拉窗帘效果
  8. 图片加载完后执行事件
  9. Prefer copy Over retain
  10. html5 css练习 定位布局
  11. Mac安装homebrew,postman,charles,switchhost
  12. “磁碟机”病毒技术分析报告
  13. 拼多多电商玩家如何利用软件机器人快速采集平台数据
  14. c语言中布尔类型字节数,【C语言】中的布尔类型
  15. windows 7 旗舰版 失效key
  16. for循环下标 shell_Shell数组操作 带下标遍历
  17. PLC通讯实现-C#实现欧姆龙以太网通讯FINS(二)
  18. C语言编程 Switch 语句编写 最简单的日历
  19. CSS 动画(圆圈荡漾+波浪图)
  20. 第一章 第一节 可充当主语的词类

热门文章

  1. php wdatepicker,WdatePicker日历控件使用方法
  2. 去耦电容(decoupling capacitor)的几个问题阐述
  3. 未能加载文件或程序集 WPFToolkit
  4. 英语语法自学网站 -------- 英语语法网
  5. CVPR 2021 VX2TEXT: End-to-End Learning of Video-Based Text Generation From Multimodal Inputs
  6. pb菜单详解和MDI
  7. 16-FreeSwitch-嵌入式脚本lua
  8. 发布项目到Jcenter
  9. Beaglebone Black 开发笔记
  10. 显示屏怎么选显示器的各种概念