动态规划是编程解题的一种重要的手段,他主要是根据一类多阶段问题的特点,把多阶段决策问题变换为一系列相互联系的单阶段问题,然后逐个加以解决,主要是用于求解具有某种最优性质的问题。我们遇到的问题可以会有许多可行解,但是我们希望找到具有最优的那个解。所以说我们就可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要他被我们计算过,就把这个结果填入表中,以供后面的使用。具体的动态规划算法多种多样,但他们具有相同的填表格式。下面来介绍一些动态规划的概念,

阶段:把所给问题的求解过程恰当地分成若干个相互联系的阶段,以便于求解。过程不同,阶段数就可能不同。描述阶段的变量成为阶段变量,
常用k表示。阶段的划分,一般是根据时间和空间的自然特征来划分。但要便于把问题的过程转化为多阶段决策的过程。
状态:状态表示每个阶段开始面临的自然状况或客观条件,他不以人们的主观意志为转移,也称为不可控因素。通常一个阶段有若干个状态,
状态通常可以用一个或一组数来描述,称为状态变量。
决策:表示当过程处于某一阶段的某一个状态时,可以做出不同的决定,从而确定下一阶段的状态,这种决定就被称为决策。不同的决策对应
着不同的数组,而描述决策的变量往往被称为决策变量。
状态转移方程:动态规划中本阶段的状态往往是上一阶段的状态和上一阶段的决策的结果,由第i段的状态f(i),和决策u(i)来确定第i+1
段的状态。状态转移表示为F(i+1)=T(f(i),u(i)),称为状态转移方程。
策略:各个阶段决策一旦确定后,整个问题的决策序列就构成了一个策略,对每个实际问题,可供选择的策略有一定的范围,称为允许策略集合。
允许策略集合中达到最有效果的的策略称为最优策略。

最优化原理:一个过程的最优决策具有这样的性质:即无论其初始状态和初始决策如何,其今后诸策略对以第一个决策所形成的状态作为初始状态
的过程而言,必须构成最优策略。也就是说一个最优策略的子策略,也是最优的。
无后效性:如果某阶段状态给定后,则在这个阶段以后过程的发展并不受这个阶段以前各个状态的影响。

这些很重要。动态规划很多实现的形式大都是由递推打表完成的,动态规划填表的形式其实都差不多,最主要的就是需要去恰当的描述事物的状态,事物的状态往往由刚开始的时间和空间所决定,需要你去把多阶段的任务分成单阶段,从此我们需要得到的状态必须接受了前面所有状态的转移,最后得到的策略必须满足最优化原理和无后效原则,而状态的阶段转移主要靠动态规划的状态转移方程。对于一个动态规划的的题目,如果我们能写出转移方程那么就简单多了,但是还应该注意初始值、数组越界的问题。

在描述事物的阶段和状态的这一步很关键,我们需要知道事物具有多个维度,多个维度能共同描述一个事物,而多个维度的值就描述了事物的状态。往往我们需要通过多个维度来描述清楚一个事物,当多维数组的值确定后,这时候就相当于确定了一个事物的一种状态。进而我们才能开始找状态转移方程。

其实你可以把动态规划理解为有最优子结构的递推。。。。。(动态的)

我们来看一道简单的题:

动态规划1动态规划1

在这道题中我们需要去描述事物的阶段和状态,阶段的话当然就是蒜头君到哪个位置,状态的话就是蒜头君到达这个位置所需要花费的最小体力,而决策的话就是蒜头君往上走还是往右走,从而我们通过dp(i+1)=T(f(i),u(i)),很显然的可以找到这样一个状态转移方程,dp(i,j)=min(dp(i+1,j),dp(i,j-1))+a(i,j),需要注意的一点就是蒜头君在第三行时只能往右走,在第一列的时候只能往上走,所以需要特判一下,有了这样一个状态转移方程之后我们可以用这个状态转移方程从(3,3)到(1,3)自左往右自下往上的去递推从而得到了蒜头君到达(1,3)最少需要花费的体力。

下面是这题的代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N=1000;
int a[N][N],dp[N][N];
int main()
{
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            cin>>a[i][j];
    for(int i=3;i>=1;i--)
    {
        for(int j=1;j<=3;j++)
        {
            if(i==3&&j==1)continue;//初始出发点的时候不需要状态转移
            else if(j==1)//特判
                dp[i][j]=a[i+1][j]+a[i][j];
            else if(i==3)//特判
                dp[i][j]=a[i][j-1]+a[i][j];
            else
                dp[i][j]=min(dp[i][j-1],dp[i+1][j])+a[i][j];//min函数求最小值,被封装在stl中
        }
    }
    cout<<dp[1][3]<<endl;
    return 0;
}

接下来我们再看一道题。

动态规划1

这是一座山,山上有许多水果,你每下一个高度就可以捡起一个水果,并且获得水果的能力。如上图所示,这是一个高度为4的小山,数字代表水果的能量,每次下一个高度,你需要选择是往左下走,还是往右下走。对于上面的情况,你能获得的最大能量为:3+1+6+5=15。现在需要你计算一下你下山能获得的最大能量是多少。

很显然,这题又是一个最优的问题,当然搜索一下肯定是可以的,但这题完全没必要,我们可以用动态规划来解决,首先来描述一下事物的阶段和状态,你下山的阶段当然是到达各个位置,而状态是到达各个位置所能获得的最大能量,决策的话要不就是往左下走,要不就是往右下走,这座小山我们在计算机中的存储结构是这样的

3
1 2
6 2 3
3 5 4 1

举个例子第三行的那个2,要不就是通过第二行的1状态转移过去要不就是通过第二行的2状态转移过去,通过这个我们很显然的可以得到一个状态转移方程为dp(i,j)=max(dp[i-1][j],dp[i-1][j-1])+a[i,j],通过这个状态转移方程之后我们自上往下自左往右的去递推到最后一行,需要注意的一点就是我们需要在每次递推的时候统计一下最大获得的能量(因为递推到最后一行之后有多个最大能量,当然你也可以遍历一下最后一行获取最大能量)。以及需要特判一下第一列。因为起点也是有能量的,所以需要注意计算一下起点的能量。

下面是这道题的代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N=10000;
const int inf=10000000;

int a[N][N],dp[N][N],ans=0;

int main()
{
    int n;
    ans=-inf;
    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++)
        {
            if(i==1)//特判起始点
                dp[i][j]=a[i][j];
            else if(j==1)//特判第一类
                dp[i][j]=dp[i-1][j]+a[i][j];
            else
                dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j];
            ans=max(ans,dp[i][j]);//因为最后一行有很多可能解,需要统计一下最优解,当然也可以遍历最后一行。
        }
    }
    cout<<ans<<endl;
    return 0;
}

这两道题我就不用打表了,应该容易理解。

相关文章:

  • 2021-05-24
  • 2022-12-23
  • 2021-06-28
  • 2021-06-01
  • 2022-01-31
  • 2021-10-18
  • 2022-03-10
猜你喜欢
  • 2021-07-27
  • 2021-08-14
  • 2022-02-18
相关资源
相似解决方案