动态规划-区间DP
分治算法中存在的问题:
- 可以分解
- 规模减小到一定程度方便求解
- 可以合并:子问题解可合并
- 子问题间不重叠
由此引入动态规划算法。
一、动态规划算法概述
- 动态规划基本思想:将待求解的问题分解成若干个子问题
- 子问题往往不是相互独立的,不同子问题的数目只有多项式量级
- 在用分治法求解时,有些子问题被重复计算了很多次
所以:如果能偶保存已解决的子问题的答案,二在需要时再找出已求的答案,就可以避免大量重复计算,从而得到多项式时间的算法。

动态规划经常用于最优化问题,有两个要素:
-
最优子问题结构性质
问题具有最优子结构性质,通俗地讲即问题的最优解包含其 子问题的最优解。
-
子问题重叠性质
子问题呈现大量的重复,称为子问题重叠性质。对于重复出 现的子问题,只需一次求解并把答案保存起来,以后直接引 用,不必重新求解,从而大大地提高解题的效率
例题:
给定一个具有N层的数字三角形,从顶至底有多条路径, 每一步可沿左斜线向下或沿右斜线向下,路径所经过的数 字之和为路径得分,请求出最小路径得分。

解法一:自顶向下求解
D(x,y):表示从顶层到达第x层第y个位置的最小路径得分。
最优子结构性质:D(x,y)的最优路径Path(x,y)一定包含子问题D(x-1,y)或D(x-1,y-1)的最优路径。否则,取D(x-1,y)和D(x-1,y-1)的最优路径中得分小的那条路径加上第x层第y个位置构成的路径得分必然小于Path(x,y)的得分,这与Path(x,y)的最优性是矛盾的。
例:D(4,2)的最优路径为2-6-1-5,它包含D(3,1)最优路径2-6-1。 因此,用二元组D(x,y)描述的计算D(x,y)的问题具有最优子结构性质。
$D(x,y)=min{D(x-1,y),D(x-1,y-1)}+a(x,y), D(1,1)=a(1,1) $其中,a(x,y)为第X层第y个位置的数值。原问题的最小路径得分可以 通过比较D(N,i)获得,其中i=1,2,…,N。 子问题重叠性质:求D(x,y)的时候,先计算D(x-1,y)和D(x-1,y-1), 下一步求D(x,y+1)时需要D(x-1,y+1)和D(x-1,y),但其中D(x-1, y)在前面已经计算过了
#include<iostream>
using namespace std;
int a[105][105];
int dp[105][105];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
cin>>a[i][j];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
}
int m=dp[1][1];
for(int i=1;i<n;i++)
m=max(m,dp[n][i]);
cout<<m<<endl;
}
解法二:自下而上求解。
二元组D(x,y):表示从第x层第y个位置到达底层的最小路径得分。
原问题的最小路径得分即为D(1,1)。
最优子结构性质:D(x,y)最优路径Path(x,y)一定包含子问题 D(x+1,y) 或 D(x+1,y+1) 的最优路径 , 否 则 , 取 D(x+1,y) 和 D(x+1,y+1)的最优路径中得分小的那条路径加上第x层第y个位置构 成的路径得分必小于Path(x,y)的得分,这与Path(x,y)的最优性矛盾。
#include<iostream>
using namespace std;
int a[105][105];
int dp[105][105];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
cin>>a[i][j];
}
for(int i=n;i>=1;i--)
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
}
cout<<dp[1][1];
}
在该问题的两个解法中,我们使用了一个二维数组dp用来存储路径的值,但是我们在每一次循环中只会使用到相邻的两行进行计算,进而,我们可以将这个二维数组优化到一维。
#include<iostream>
using namespace std;
int a[105][105];
int dp[105];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
cin>>a[i][j];
}
for(int i=n;i>=1;i--)
for(int j=1;j<=i;j++)
{
dp[j]=max(dp[j],dp[j+1])+a[i][j];
}
cout<<dp[1];
}
二、区间DP
1.矩阵连乘问题
相同的若干个矩阵相乘,由于各矩阵的维度不同,矩阵相乘的先后顺序会导致相乘的次数不同。
例如:4个矩阵相乘实际上有5种方法:
A:50X10 B:10X40 C:40X30 D:30X5

完全加括号的矩阵连乘积可递归地定义为:
单个矩阵是完全加括号的;
矩阵连乘积A是完全加括号的,则A可表示为 2个完全加括号的矩阵连乘积B和C的乘积并加括号,即 A = (BC)
解法一:穷举法
列举出所有可能的计算次序,并计算出每 一种计算次序相应需要的数乘次数,从中找出一种数乘次数最少的计算次序。
算法复杂度分析: 对于n个矩阵的连乘积,设其不同的计算次序为P(n)。 由于每种加括号方式都可以分解为两个子矩阵的加括号 问题(A1...Ak)(Ak+1…An)可以得到关于P(n)的递推式如下:
解法二:动态规划
• 首先分析最优解的结构 •
-
特征:计算\(A[i:j]\)的最优次序所包含的计算矩阵子链 \(A[i:k]\)和\(A[k+1:j]\)的次序也是最优的。
-
矩阵连乘计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。
-
问题的最优子结构性质是该问题可用动态规划算法求解的显著(最关键)特征.
• 建立递归关系 •
考虑当如下在Ak点划分时,m[i,j]如何计算?
\((A_i *A_{i+1}*…*A_k) (A_{k+1} *…*A_j )\)
\(M[i,k] + M[k+1,j] +P_{i-1}P_kP_j\)
可以递归地定义\(m[i,j]\)为:

k的位置只有j-i种可能
/*参数说明
n:矩阵的个数
p[i]:每个矩阵的第二个维度
m[i][j]:用来存放i->j之间的最优值
s[i][j]:保存i->j的分割点
*/
void MatrixChain(int *p,int n,int **m,int **s) {
for(int i=1; i<=n; i++)
m[i][i] = 0;//只有一个矩阵时,不用相乘,所以是0
for(int r=2; r<=n; r++)//r个矩阵连乘
for(int i=1; i<=n-r+1; i++) {
//r个矩阵的r-1个间隙依次测试最优点
int j=i+r-1;
m[i][j] = m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j] = i;
for(int k=i+1; k<j; k++) {//循环变量k为分割点
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j]) m[i][j] = t,s[i][j] = k;
}
}
}
凸多边形三角剖分
-
多边形的三角剖分是将多边形分割成互不相交的三角形。
-
由多边形的边和弦组成的三角形上的权w(即三边和)。
-
要求确定该凸多边形的一个三角剖分,使得即该三角剖分中诸三角形上权之和为最小。
用多边形顶点的逆时针序列表示凸多边形 , 即 \(P={v_0,v_1,…,v_{n-1}v_n}\)
表示具有n+1条边的凸多边形。
public static void minWeightTriangulation(int n) {
for (int i = 1; i <= n; i++) {
t[i][i] = 0;
}
for (int r = 2; r <= n; r++) {//i与j的差值
for (int i = 1; i <= n - r + 1; i++) {
int j = i + r - 1;
m[i][j] = m[i + 1][j] + w(i-1,i,j);//k==i的情况
s[i][j] = i;
for (int k = i + 1; k < i+r-1; k++) {
int u = m[i][k] + m[k + 1][j] + w(i-1,k,j);
if (u < m[i][j]) {
m[i][j] = u;
s[i][j] = k;
}
}
}
}
}

浙公网安备 33010602011771号