动态规划4.1——基础动态规划问题
题目上添加了超链接,大家点一下题目就会自动跳转到Poj原题界面~~ 冲鸭冲鸭ヾ(◍°∇°◍)ノ゙。
预警、预警:本章节开始,再次劝退!
但我们也自此才开始接触到算法的精髓。可以说之前所学习的方法都是我们的解题手段,但自此章节开始,我们所应对的变成了完整的综合题。动态规划问题类型很广,在算法问题中占比很大。简单有白给模板题,难起来可以难的没边。
在应用动态规划方法解决问题时,关键是确定当前问题的所有子问题的最优子结构,再构造出原问题的解,也就是多阶段决策中的转移方程。转移方程的通常具有这样的特点:当前阶段的值与所有之前阶段值的最优值关联。动态规划所求为问题全局最优解。
动态规划题目特点:(有重叠的子问题)
1:计数
—有多少种方式走到右下角
—有多少种方法选出k个数使得和为Sum
2:求最大最小值
—从左上角走到右下角路径的最大数字和
—最长上升子序列长度
3:求存在性
—取石子游戏,先手是否必胜
—能不能选出k个数使得和是Sum
动态规划组成部分:
1:确定状态
—确定最后一步(最优策略)
—抽象子问题
2:归纳转移方程
3:初始条件和边界情况
4:计算顺序
4.1.1 The Triangle (1163/3176)
题意:一个数字三角形如图4.1所示,从顶部出发,每一个结点可以选择向左或向右走,要求找到一个从上到下的路径,路径上数字的和最大。
小笔记:简单入门题,需要注意思想,从这里出发理解动态规划。
#include <cstdio> #include <algorithm> using namespace std; int main() { int n; int D[355][355]; //存储到该点的最大路径 scanf("%d", &n); for (int i = 0; i < n; i++) for (int j = 0; j <= i; j++) scanf("%d", &D[i][j]); for (int i = n - 2; i >= 0; i--) for (int j = 0; j <= i; j++) D[i][j] += max(D[i + 1][j], D[i + 1][j + 1]); printf("%d\n", D[0][0]); return 0; }
4.1.2 Brackets (2955)
题意:给出一个括号序列,找出最长的规范括号子序列。
小笔记:区间dp、记忆化搜索
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int N = 101; //如果a,b为左右成对的括号,则返回长度2,否则返回0。 int f(char a, char b) { return ((a == '[' && b == ']') || (a == '(' && b == ')')) ? 2 : 0; } int main() { char s[N]; //括号序列 while (scanf("%s", s) && s[0] != 'e') { int D[N][N] = {0}; //记录s[i]~s[j]之间规范括号子序列的长度 int m = strlen(s); //m为区间总长度 for (int t = 1; t <= m - 1; t++) //t为子区间长度 for (int i = 0; i <= m - t - 1; i++) { int j = i + t; for (int k = i; k <= j - 1; k++) D[i][j] = max(D[i][j], max(D[i][k] + D[k + 1][j], D[i + 1][j - 1] + f(s[i], s[j]))); } printf("%d\n", D[0][m - 1]); } return 0; }
4.1.3 Multiplication Puzzle (1651)
题意:n个数排成一排,从中间位置取走一个数,和它前后两个数相乘得到一个乘积值,再取一个数,继续计算,直到剩下的最后三个数相乘,将所有乘积相加,求这个和的最小值。
小笔记:区间dp、矩阵连乘问题
题解:用a[1…n]保存n个数,D[i,j]为i到j区间计算出来的和的最小值,区间长度s=j-i,在区间内取一个位置k,则D[i,j]为所有D[i,k]+D[k,j]+a[i]*a[k]*a[j]的最小值。
#include <cstdio> #include <algorithm> #include <climits> using namespace std; const int N = 105; int main() { int n; int a[N]; int D[N][N] = {0}; //D[i,j]为i到j区间计算出来的和的最小值 scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int s = 2; s <= n - 1; s++) //s为区间长度 for (int i = 1; i <= n - s; i++) { int j = i + s; D[i][j] = INT_MAX; for (int k = i + 1; k < j; k++) //k为区间内某位置 D[i][j] = min(D[i][j], D[i][k] + D[k][j] + a[i] * a[k] * a[j]); } printf("%d\n", D[1][n]); return 0; }
4.1.4 Apple Catching (2385)
题意:两个苹果树1和2,在t分钟内每分钟有1棵树向下落1个苹果,在树下接苹果,初始位置在1号树下,换一棵树记作交换1次位置,最多可换w次位置,计算最多可以接多少苹果。
小笔记:经典dp,不难,注意细节。
#include <cstdio> #include <algorithm> using namespace std; int main() { int t, w; scanf("%d%d", &t, &w); int D[35] = {0}; while (t--) { int x; scanf("%d", &x); D[0] += x % 2; //每个时间对D[0]进行更新 for (int i = 1; i <= w; i++) D[i] = max(D[i], D[i - 1]) + (i + x) % 2; } printf("%d\n", D[w]); return 0; }
4.1.5 Hamming Problem (2545)
题意:定义一个递增序列,序列中的数字是只包含p1、p2、p3三个素数因子的所有自然数,输出序列中的第i个数。
题解:简单题。用数组h保存结果,用x1、x2、x3分别保存h中的数字与p1、p2、p3相乘的结果,由序列的属性可知,x1、x2、x3这3个值都属于h并且都是从1开始依次递增。用k1、k2、k3记录x1、x2、x3在h中取的值的位置,当被“使用”过之后,对应的位置加1。
h在求解的过程中依赖于之前子问题的解,并取一个最小值作为当前值,也是动态规划思想的体现。
#include <cstdio> #include <algorithm> using namespace std; long long h[1000000]; int main() { int p1, p2, p3, i; while (~scanf("%d%d%d%d", &p1, &p2, &p3, &i)) { h[0] = 1; int k1, k2, k3; k1 = k2 = k3 = 0; for (int k = 1; k <= i; k++) { long long x1 = h[k1] * p1; long long x2 = h[k2] * p2; long long x3 = h[k3] * p3; h[k] = min(min(x1, x2), x3); if (h[k] == x1) k1++; if (h[k] == x2) k2++; if (h[k] == x3) k3++; } printf("%lld\n", h[i]); } return 0; }