本文内容基于《算法笔记》和官方配套练题网站“晴问算法”,是我作为小白的学习记录,如有错误还请体谅,可以留下您的宝贵意见,不胜感激。
一、最小消耗能量
搜索肯定超时,动态规划求解如下:
状态:dp[i]表示处在第i个高台时消耗的最小能量;
状态转移方程:
dp[i] = max(dp[i + 1] + abs(h[i] - h[i + 1]), dp[i + 2] + abs(h[i] - h[i + 2]));
可以画出树状图,然后递推从边界求解,算是最原始的动态规划,和斐波那契差不多。
边界处理:
dp[n - 1] = 0;
dp[n - 2] = abs(h[n - 1] - h[n - 2]);
完整代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 10e4;
int h[MAXN] = {};
int dp[MAXN] = {};
int main(){
int n;
scanf("%d", &n);
for(int i = 0; i <= n - 1; i++)
scanf("%d", h + i);
dp[n - 1] = 0;
dp[n - 2] = abs(h[n - 1] - h[n - 2]);
for(int i = n - 3; i >= 0; i--)
dp[i] = min(dp[i + 1] + abs(h[i] - h[i + 1]) , dp[i + 2] + abs(h[i] - h[i + 2]));
printf("%d", dp[0]);
}
二、矩阵最大权值
搜索肯定超时,动态规划求解如下:
状态:dp[i][j]表示处在i行j列时路径上的最大权值之和;
状态转移方程:
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) + matrix[i][j];
这个问题和数塔类似,算是比较简单了。
完整代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 100 + 1;
int matrix[MAXN][MAXN];
int dp[MAXN][MAXN] = {0};
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &matrix[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) + matrix[i][j];
}
}
printf("%d", dp[n][m]);
return 0;
}
三、非连续数位序列
这个是序列问题,所以肯定需要一个维度i来表示序列下标,代表以i为结尾的不连续0的序列数,但一个维度还不能把状态写出来。这个问题可以分解为两个子问题,即当前位置放0或者非0,而子问题的选择依赖于i-1位置上的元素是0还是非0,所以需要再开一个维度来存储当前位置上元素的选择是0还是非0,故状态设计为dp[i][v],状态转移方程为:
dp[i][v] = dp[i - 1][1] (v= =0)
dp[i - 1][0] + dp[i - 1][1]) * 9 (v= =1)
其中,当v为0时代表当前位置为0,当v为1时代表当前位置为非0,将结果对10007取模。
这个没有题解,所以也不知道这样写算不算标准······
完整代码如下:
#include<cstdio>
const int MAXN = 10e4;
int dp[MAXN][2] = {};
int main(){
int n;
scanf("%d", &n);
dp[0][0] = 1; //状态边界
dp[0][1] = 9;
for(int i = 1; i <= n - 1; i++)
for(int v = 0; v <= 1; v++){
if(v == 0) dp[i][v] = dp[i - 1][1] % 10007;
else dp[i][v] = ((dp[i - 1][0] + dp[i - 1][1]) * 9) % 10007;
}
printf("%d", (dp[n - 1][0] + dp[n - 1][1]) % 10007);
}
四、最小涂色成本
这道题和上道题类似,属于“连续性”问题,也是用二维状态dp[i][j],i表示前i张纸可以构成的最小成本,j表示当前位置的纸张颜色的选择。
状态转移方程为:
dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + a[i];
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + b[i];
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + c[i];
其中j=1,2,3代表三种颜色,分析方法与上道题类似。
完整代码如下:
include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 10000;
int a[MAXN], b[MAXN], c[MAXN];
int dp[MAXN][3];
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d%d%d", &a[i], &b[i], &c[i]);
}
dp[0][0] = a[0]; //状态边界
dp[0][1] = b[0];
dp[0][2] = c[0];
for (int i = 1; i < n; i++) {
dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + a[i];
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + b[i];
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + c[i];
}
printf("%d", min(min(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]));
return 0;
}
五、编辑距离
这道题和最长公共子序列(LCS)类似,刚开始没有想出来,参照大佬的解析,放一个传送门:http://t.csdn.cn/2MWkR
备注
1.分治和动态规划最难的地方就是如何分解子问题,如何用状态表示当前问题,以及如何通过状态转移构造出完整的dp数组,这就得靠刷题找经验了······
2.动态规划等价于有向无环图,有起始节点和终止节点,本质是有限状态自动机,任何一个非起始节点都可以从其他节点转移过来;
3.注意如何通过dp表构造满足最优解的具体方案