一.动态规划算法的基本要素[^2]
-
最优子结构
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。
在分析问题的最优子结构性质时,所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。
注意:同一个问题可以有多种方式刻划它的最优子结构,有些表示方法的求解速度更快(空间占用小,问题的维度低) -
重叠子问题
递归算法求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题的重叠性质。
动态规划算法,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。
通常不同的子问题个数随问题的大小呈多项式增长。因此用动态规划算法只需要多项式时间,从而获得较高的解题效率。
动态规划算法是从暴力搜索算法优化过来的,如果我们不清楚暴力搜索的过程,就难以理解动态规划的实现,当我们了解了动态规划算法的基本原理的文字概述,实现条件之后,这时可能并不是太理解这种思想,去面对实际问题的时候也是无从下手,这个时候我们不能停留在文字层面上,而应该去学习经典动态规划算法的实现,然后倒回来看这些概念,便会恍然大悟。
动态规划算法的难点在于 从实际问题中抽象出动态规划表dp,dp一般是一个数组,可能是一维的也可能是二维的,也可能是其他的数据结构。
二.动态规划的关键点:
1、最优化原理,也就是最有子结构性质。这指的是一个最优化策略具有这样的性质,无论过去状态和决策如何,对前面的决策所形成的状态而言,余下的决策必须构成最优策略,简单来说就是一个最优化策略的子策略总是最优的,如果一个问题满足最优化原理,就称其有最优子结构性质。
2、无后效性,指的是某个状态下的决策的收益,只与状态和决策相关,与达到该状态的方式无关。
3、子问题的重叠性,动态规划将原来指数级的暴力搜索算法改进到了具有多项式时间复杂度的算法,其中的关键在于解决了荣誉,重复计算的问题,这是动态规划算法的根本目的。
4、总体来说,动态规划算法就是一系列以空间换取时间的算法。
三. 例题
例1:最大m子段和问题
描述:给定N(1 <= N <= 100000)个绝对值不超过32768的整数( 可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出M个连续不相交子段,使这M个子段的和最大。如果子段全都是负数则最大子段和也为负数。
输入:有一个正整数M和一个正整数N,后面紧跟N个绝对值不大于32768的整数
输出:最大M子段和
样例输入:
8 2 6 -1 4 -2 3 -2 3
样例输出:
8
最大m子段和问题
令dp[i][j]表示在前i个数中选取j段且第i个数在最后一组中的最大子段和
那么现在对于第i个数有两种决策
第i个数和第i-1个数连接成一段
dp[j][i]= dp[i-1][j] + a[i];
第i个数自己单独做一段.那么前面就需要有j-1段
dp[i][j] = max{dp[k][j-1]<|j-1<=k<i}+a[i];
就有了状态转移方程
dp[i][j]=max(dp[i-1][j],
max{dp[k][j-1]|j - 1<= k < i})+a[i];
代码如下:
const int N= 10005, INF= 1e9;
int a[N], dp[N][N];
int main()
{
int n, m;
while(~scanf("%d%d", &m, &n))
{
for(int i=1;i<=n; i++)
scanf("%d", &a[i]);
memset(dp, 0, sizeof (dp));
int ans = -INF;
for(int j=1;j<=m; j++)
{
for(int i=j;i<=n; i++)
{
dp[i][j]=i-1<j?-INF:dp[i-1][j];
for(int k=j-1 ;k<i;++k)
dp[i][j]=max(dp[i][j],dp[k][j-1]);
dp[i][j]+=a[i];
if(j == m) ans=max(ans,dp[i][j]);
}
}
printf("%d\n", ans);
}
return 0;
}
例2.线段覆盖
数轴上有n条线段,线段的两端都是整数坐标,坐标范围在0~ 1000000,每条线段有一个价值,请从n条线段中挑出若干条线段,使得这些线段两两不覆盖(端点可以重合)且线段价值之和最大。n<=1000
输入描述Input Description
第一行一个整数n,表示有多少条线段。
接下来n行每行三个整数, ai bi ci,分别代表第i条线段的左端点ai,右端点bi (保证左端点<右端点)和价值ci。
输出描述Output Description
输出能够获得的最大价值
样例输入Sample Input
3
1 2 1
2 3 2
1 3 4
样例输出Sample Output
4
按照右区间坐标排个序再来DP,DP[i]=max{DP[j], 1<=j<i}+第i条线段的价值。
代码如下:
#define get(x) scanf("%d", &x)
#define put(x) printf("%d", x)
#define cIs(x) memset(x, 0, sizeof(x))
using namespace std;
int n, res;
int dp[1010];
struct L
{
int I,r,c;
}a[1010];
bool cmp(L a, L b)
{
return a.r < b.r;
}
int main()
{
get(n);
{
for (int i=1;i<=n; i++)
{
get(a[i].I), get(a[i].r), get(a[i].c);
}
sort(a+1, a+n+1, cmp);
cls(dp);
for (int i=1;i<=n; i++)
{
int maxx= 0;
for (int j=1;j<i;j++)
if (a[i].I >= a[j].r)
maxx = max(maxx, dp[j]);
dp[i] = maxx + a[i].c;
res = max(res, dp[i]);
}
put(res);
return 0;
}