动态规划-区间DP

分治算法中存在的问题:

  • 可以分解
  • 规模减小到一定程度方便求解
  • 可以合并:子问题解可合并
  • 子问题间不重叠

由此引入动态规划算法。

一、动态规划算法概述

  • 动态规划基本思想:将待求解的问题分解成若干个子问题
  • 子问题往往不是相互独立的,不同子问题的数目只有多项式量级
  • 在用分治法求解时,有些子问题被重复计算了很多次

所以:如果能偶保存已解决的子问题的答案,二在需要时再找出已求的答案,就可以避免大量重复计算,从而得到多项式时间的算法。

动态规划经常用于最优化问题,有两个要素:

  1. 最优子问题结构性质

    问题具有最优子结构性质,通俗地讲即问题的最优解包含其 子问题的最优解。

  2. 子问题重叠性质

    子问题呈现大量的重复,称为子问题重叠性质。对于重复出 现的子问题,只需一次求解并把答案保存起来,以后直接引 用,不必重新求解,从而大大地提高解题的效率

例题:

给定一个具有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)的递推式如下:

\[P(n)=\begin{cases}1&n = 1\\\sum_{k=1}^{n-1}P(k)P(n-k)&n>1\end{cases} ->P(n)=Ω(\frac{4^{(n-1)}}{(n-1)^{3/2}})=Ω(2^n) \]

解法二:动态规划

• 首先分析最优解的结构 •

\[将矩阵连乘积A(i)*...*A(j)简记为A[i:j],这里i≤j\\ 考察计算A[i:j]的最优计算次序。\\ 设这个计算次序在矩阵A_k和A_{k+1}之间将矩阵链断开,i≤k<j\\ 计算量:A[i:k]的计算量加上A[k+1:j]的计算量,\\ 再加上A[i:k]和A[k+1:j]相乘的计算量 \]

  • 特征:计算\(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;
			}
		}
}

image-20240405010133816

凸多边形三角剖分

  • 多边形的三角剖分是将多边形分割成互不相交的三角形。

  • 由多边形的边和弦组成的三角形上的权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;
              }
          }
      }
  }
}
posted @ 2024-04-05 01:25  YJQING  阅读(37)  评论(0)    收藏  举报