动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法。
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
动态规划的一般思路
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。
实际应用中可以按以下几个简化的步骤进行设计:
(1)分析最优解的性质,并刻画其结构特征。
(2)递归的定义最优解。
(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值
(4)根据计算最优值时得到的信息,构造问题的最优解
动态规划算法实现的说明
动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。
使用动态规划求解问题,最重要的就是确定动态规划三要素:
(1)问题的阶段
(2)每个阶段的状态
(3)从前一个阶段转化到后一个阶段之间的递推关系。
#include <iostream>
#include <algorithm>
using namespace std;
int D[505][505];
int n;
int maxsum[505][505];
/*递归法
int MaxSum(int i,int j)
{
//表示已经计算过这个值了,可以直接取 不用重复计算
if( maxsum[i][j] != -1 )
return maxsum[i][j];
//最后一行
if(i == n-1)
maxsum[i][j] = D[n-1][j];
else{
maxsum[i][j] =max(MaxSum(i+1,j+1),MaxSum(i+1,j))+D[i][j];
}
return maxsum[i][j];
}
*/
int main()
{
cin >> n;
for(int i = 0;i < n;i++)
for(int j = 0;j <= i;j++)
{cin >> D[i][j];
maxsum[i][j] = -1; }
//递推法,从下往上计算
for(int j = 0;j < n;j++)
maxsum[n-1][j] = D[n-1][j];
for(int i = n-2;i>=0;i--)
for(int j = 0;j <=i;j++)
maxsum[i][j] = max(maxsum[i+1][j],maxsum[i+1][j+1])+D[i][j];
cout << maxsum[0][0];
return 0;
}
这个题 是需要从下往上算的,将数字三角形的底层的值赋给maxsum数组的最后一层
然后在用for循环 加上上层的数字 (只能i+1(上) 或者i+1,j+1(左上))
每次循环都更新最大值
最后最大值就是maxsum[0][0]啦
首先需要知道,子串和子序列的概念,我们以字符子串和字符子序列为例,更为形象,也能顺带着理解字符的子串和子序列:
(1)字符子串:指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。
(2)字符子序列:指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 1005, INF = 0x7f7f7f7f;
int a[maxn], f[maxn];
int n,ans = -INF;
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
f[i] = 1;
}
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j] < a[i])
f[i] = max(f[i], f[j]+1);
for(int i=1; i<=n; i++)
ans = max(ans, f[i]);
printf("%d\n", ans);
return 0;
}
1. 集合: 所有以 a[i] 结尾的上升子序列的集合
数(属性) : max
2. 计算集合:
f[i] = max(f[1], f[2], ... f[i - 1]) + 1;
3. 边界(初始化) f[0] = 0, f[i] = 1; i >= 1
4. 时间复杂度 O(n^2)
公共子序列:如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序列A和序列B的公共子序列。如对序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 来说,序列1,8,7是它们的一个公共子序列。
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
char a[N], b[N];
int main()
{
cin >> n >> m;
cin >> a + 1 >> b + 1;
for (int i = 0; i <= n; i ++ ) f[i][0] = 0;
for (int i = 0; i <= m; i ++ ) f[0][i] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j]) f[i][j] = max(f[i - 1][j - 1] + 1, f[i][j]);
}
cout << f[n][m] << endl;
return 0;
}
这是我们的老师的分析过程:
f[i, j]
1. 集合:
所有在a 的 前 i 个字母出现,在 b 的前 j 个字母出现 的公共子序列的集合
集合的 Max
2. 计算集合:
f[i, j] 一定是一下其中一种情况
a[i] 不在公共子序列中, b[j] 不在公共子序列中
f[i, j] = f[i - 1, j - 1];
a[i] 不在公共子序列, b[j] 在
f[i, j] = f[i - 1, j]
a[i] 在, b[j] 不在
f[i, j] = f[i, j - 1];
a[i] 在, b[j] 在
if (a[i] == b[j])
f[i, j] = f[i - 1, j - 1] + 1;
初始化(边界)
3. f[0, i] = 0, f[i, 0] = 0;
4. O(n^2)