跳到这学完递归和分治,先让我学学动态规划(*^_^*)
第 12 章 动态规划
12.1 递归求解
例:N 阶楼梯上楼问题(华科大)
题目描述:
代码表示:
1、初级复杂度会很高,有很多是被重复计算的,使用简单递归。
2、写一个预留数组来优化,防止重复被算
3、使用动态规划,除了1和2以外从3开始从小问题直接出手。
#include <bits/stdc++.h>
using namespace std;
//1、普通递归
int feibo(int n){
if(n==1||n==2){
return n;
}else{
return feibo(n-1)+feibo(n-2);
}
}
//2、设置数组来实现记忆化
int F[100];
int feibo2(int n){//记忆化
if(F[n]!=-1){//防止被算多次
return F[n];
}
if(n==1||n==2){
F[n]=n;
return n;
}else{
F[n]=feibo2(n-1)+feibo2(n-2);
return F[n];
}
}
//3、动态规划
int dp[100];
int feibo3(int n){
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
int main() {
for(int i=0;i<100;++i){
F[i]=-1;//如果开始没有被算过先置-1
}
int n;
while (scanf("%d",&n)!=EOF){
//printf("%d",feibo1(n));
//printf("%d",feibo2(n));
printf("%d",feibo3(n));
}
return 0;
}
动态规划总结:
例:最大序列和(清华上机)
题目描述:
代码表示:
#include <bits/stdc++.h>
using namespace std;
#define N 1000000
long long a[1000001];
long long dp[1000001];
long long maxsub(int n){
long long maximun=-INT_MAX;
for(int i=0;i<n;++i){
if(i==0){
dp[i]=a[i];
}else{
dp[i]==max(a[i],a[i]+dp[i-1]);
}
maximun=max(maximun,dp[i]);
}
return maximun;
}
int main() {
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i){
scanf("%lld",&a[i]);
}
long long answer=maxsub(n);
printf("%d",answer);
}
return 0;
}
心得体会:
1、代码中的 maxsub
函数使用动态规划的思想来计算给定数组 a
的最大子序列和。动态规划通过将原问题分解为子问题,并利用子问题的解来求解原问题。在这个代码中,dp
数组用于存储以当前位置为结尾的子数组的最大和。dp[i]
表示以第 i
个元素结尾的子数组的最大和。通过遍历数组 a
,从头到尾计算每个位置的最大子序列和,并将结果保存在 dp
数组中。
具体计算每个位置的最大子序列和的过程如下:
- 当
i
等于 0 时,即考虑以第一个元素结尾的子数组,它的最大和就是第一个元素本身,所以dp[0] = a[0]
。 - 当
i
大于 0 时,考虑以当前位置i
结尾的子数组。有两种情况:- 如果将当前元素
a[i]
加入到之前的最大子序列和中能够得到更大的和,那么就将其加入,即dp[i] = a[i] + dp[i-1]
。 - 如果将当前元素
a[i]
加入到之前的最大子序列和中不能得到更大的和,那么就不加入,即dp[i] = a[i]
。
- 如果将当前元素
- 在每次计算
dp[i]
的过程中,都维护一个全局变量maximun
来记录当前的最大子序列和。
最后,遍历完整个数组后,maximun
就存储了整个数组的最大子序列和,函数返回这个最大值。动态规划在这段代码中的作用是通过 dp
数组计算并返回给定数组的最大子序列和。
2、动态规划在算法设计中具有以下好处:
1)提高效率:动态规划可以通过将问题分解为子问题并保存子问题的解来避免重复计算。通过记忆化或者建立动态规划表格,可以避免重复计算子问题,从而大大提高算法的效率。
2)简化问题:动态规划可以将原问题分解为一系列的子问题,使得问题的解决变得更加简单和直观。通过将复杂的问题分解为简单的子问题,可以减少思考和实现的复杂度。
3)解决优化问题:动态规划常用于求解最优化问题,如最大值、最小值等。通过定义状态和状态转移方程,动态规划可以找到问题的最优解。
4)可行性分析:动态规划可用于判断问题是否具有可行解。通过动态规划的状态转移过程,可以判断是否存在满足特定条件的解。
5)算法设计的一种思想:动态规划是一种常用的算法设计思想,可以用于解决多种问题。学习和理解动态规划的思想和方法,有助于培养抽象建模和问题解决的能力。
例:最大上升子序列和(北大上机)
题目描述:
代码表示:
#include <bits/stdc++.h>
using namespace std;
int a[1010];
int dp[1010];
int main() {
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<n;++i){
scanf("%d",&a[i]);
}
int answer=0;
for(int i=0;i<n;++i){
dp[i]=a[i];
for(int j=0;j<i;++j){
if(a[j]<a[i]){
dp[i]=max(dp[i],dp[j]+a[i]);
}
}
answer=max(answer,dp[i]);
}
printf("%d",answer);
}
return 0;
}
例:最长公共子序列
题目描述:
思路提示:
代码表示:
#include <bits/stdc++.h>
using namespace std;
#define N 1001
int dp[N][N];
int main() {
int m,n;
char s1[N];
char s2[N];
scanf("%d%d",&n,&m);
scanf("%d%d",s1,s2);
for(int i=0;i<n;++i){
for(int j=0;j<=m;++j){
if(i==0||j==0){
dp[i][j];
continue;
}
if(s1[i-1]==s2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}
else{
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
}
printf("%d\n",dp[n][m]);
}
心得体会:
dp
数组的作用:使用动态规划解决最长公共子序列问题。
总结代码的执行过程如下:
1、从输入中读取两个整数 n
和 m
,分别表示两个字符串的长度。
2、从输入中读取两个字符串 s1
和 s2
。
3、创建一个二维数组 dp
,其中 dp[i][j]
表示字符串 s1
的前 i
个字符和字符串 s2
的前 j
个字符的最长公共子序列的长度。
4、使用双重循环遍历数组 dp
,其中外层循环变量 i
从 0 到 n-1
,内层循环变量 j
从 0 到 m
。
5、在每个位置 (i, j)
上,根据以下情况更新 dp[i][j]
:
1)当 i
或 j
为 0 时,表示一个字符串为空,此时 dp[i][j]
为 0。
2)当 s1[i-1]
等于 s2[j-1]
时,表示当前字符相等,最长公共子序列可以在之前的最长公共子序列的基础上加上这个字符,即 dp[i][j] = dp[i-1][j-1] + 1
。
3)当 s1[i-1]
不等于 s2[j-1]
时,表示当前字符不相等,最长公共子序列长度取决于之前的最长公共子序列。可以选择舍弃 s1[i-1]
或 s2[j-1]
中的一个字符,即 dp[i][j] = max(dp[i][j-1], dp[i-1][j])
。
6、循环结束后,dp[n][m]
存储的就是字符串 s1
和 s2
的最长公共子序列的长度。