11.1 动态规划的递归写法与递推写法
11.1.1 什么是动态规划
11.1.2 动态规划的递归写法(斐波那契)
#include <cstdio>#include <string.h>const int maxn = 10010;int dp[maxn];int F(int n){if(n == 0 || n == 1){return 1;}if(dp[n] != -1){return dp[n];}else{dp[n] = F(n-2) + F(n-1);return dp[n];}}int main(){memset(dp, -1, sizeof (dp));int r = F(5);printf("%d\n",r);return 0;}
如果使用大一学的普通的递归,那复杂度就是O(2**n**), 对于稍微大一些的工作量就无法承担。因此,使用记忆化搜索显得尤为重要,不需要不断地重复计算,可以将复杂度降到O(n)。
psps: 之前不能AC是因为赋初值的问题,int dp[maxn] = {-1},只是将第一个数赋值为-1,其余仍为0,因此需要使用memset.
11.1.3 动态规划的递推写法(数塔问题)(P427)
动态规划的递推写法就是从边界出发,通过状态转移方程扩散到整个dp数组。
以数塔问题为例,最后一层的dp就是f的值,然后依次一层一层利用状态转移方程往上推,最后dp[1][1]的值即为最终解。
#include <cstdio>#include <algorithm>using namespace std;const int maxn = 10010;int f[maxn][maxn], dp[maxn][maxn];int main(){int n,k;scanf("%d",&n);for(int i = 1; i <= n; ++i){for(int j = 1; j <= i; ++j){scanf("%d",&f[i][j]);}}// 设置边界,最后一层dp值即为f值for(int j = 1; j <= n; ++j){dp[n][j] = f[n][j];}for(int i = n-1; i >= 1; --i){for(int j = 1; j <= i; ++j){dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + f[i][j];}}printf("%d\n",dp[1][1]);return 0;}
11.2 最大连续子序列和
// P430 最大连续子序列和#include <cstdio>#include <algorithm>using namespace std;const int maxn = 10010;int A[maxn], dp[maxn]; // A[i]存放序列,dp[i]存放以A[i]结尾的连续序列的最大和int main(){int n;scanf("%d",&n);for(int i = 0; i < n; ++i){scanf("%d",&A[i]);}// 边界dp[0] = A[0];// dp[0]已赋值,下面从1~n-1赋值for(int i = 1; i < n; ++i){// 状态转移方程dp[i] = max(A[i], dp[i - 1] + A[i]);}// dp[i]存放以A[i]结尾的连续序列的最大和,需要遍历i得到最大的才是结果int k = 0;for(int i = 1; i < n; ++i){if(dp[i] > dp[k]){k = i;}}printf("%d\n",dp[k]);return 0;}
11.3 最长不下降子序列(LIS)
#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 = 1; i <= n; ++i){scanf("%d",&A[i]);}int ans = -1;for(int i = 1; i <= n; ++i) // 按顺序计算出dp[i]的值{dp[i] = 1; // 边界初始条件// 接上,因为dp[i]要么就是A[i]自己,要么就是某下标j,dp[j]+1for(int j = 1; j < i; ++j){if(A[j] <= A[i] && dp[j] + 1 > dp[i]){// 状态转移方程,用以更新dp[i]dp[i] = dp[j] + 1;}}ans = max(dp[i], 1);}printf("%d\n",ans);return 0;}
动态规划问题的核心就是涉及状态转移方程,以及边界问题。此问题类似于最大连续子序列之和的问题,分别遍历1~n,把A[i}作为结尾元素分别更新dp[i].
11.4 最长公共子序列
题解:
此题的核心是设计状态转移函数和边界。
// P434 最长公共子序列(LCS)#include <cstdio>#include <algorithm>#include <cstring>using namespace std;const int maxn = 10010;char A[maxn], B[maxn];int dp[maxn][maxn];int main(){// int n;gets(A + 1);gets(B + 1);int lenA = strlen(A + 1);int lenB = strlen(B + 1);for(int i = 1; i <= lenA; ++i){dp[i][0] = 0;}for(int j = 1; 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;}
