手把手教你学初级动态规划

194 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天

编译器推荐

强烈推荐Clion

本文任务

  1. 说明动态规划算法的基本要素。
  1. 理解最长公共子序列求解的基本方法,并利用动态规划方法编程实现最长公共子序列算法。
要求从文件读入或者键盘输入两个序列,通过算法得出这两个序列的最长公共子序列。输出结果保存在文件或者输出到屏幕显示。
  1. 理解最大子段和算法,并利用动态规划方法编程实现求解最大子矩阵和问题。
要求从文件读入或者从键盘输入一个较大矩阵,通过算法得出这个矩阵中最大的子矩阵和及相应的子矩阵范围。

要素

动态规划性质: 1 最优子结构性质 2 子问题重叠性质 ----->该问题可用动态规划算法求解的基本要素

1 最优子结构

当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。最优子结构性质提供了该问题的可用动态规划算法求解的重要线索。
动态规划,利用问题的最优子结构性质,以自底向上的方式递归的从子问题的最优解逐步构造出整个问题的最优解。

2 重叠子问题

动态规划,避开了递归时,重复的计算相同子问题的过程,对每个子问题只解一次,而后将其保存在一个表格中,当再次需要的时候,只是简单的用常数时间查看一下结果。

3 备忘录方法

递归方式自顶向下

第一道题(经典最长公共子序列)

  1. 设计步骤:

状态分成两半考虑比较方便,按两个序列末尾的字符是不是相等来区分。

Drawing 2022-10-01 16.53.38.excalidraw.png

如果两个字符相等,就可以直接转移到dp[i-1][j],不相等的话,两个字符一定有一个可以抛弃,可以对dp[i-1][j],dp[i][j-1]两种状态取max来转移。需要注意的是顺序是逆序。

#include<iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1100;
int n, m;
int a[N], b[N];
int dp[N][N];
​
int main()
{
    cin >> n >> m;
   for(int i=1;i<=n;i++)cin>>a[i];
   for(int j=1;j<=m;j++)cin>>b[j];
​
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <=m; j ++ )
            if(a[i]==b[j]){
                dp[i][j]=dp[i-1][j-1]+1;
            }else{
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
    cout << dp[n][m] << endl;
​
    vector<int >res;//逆序
    for (int i = n, j = m; i && j; )
    {
        if (a[i] == b[j])  res.push_back(a[i]), i --, j --;
        else if (dp[i - 1][j] > dp[i][j - 1]) i --;
        else j --;
    }
    reverse(res.begin(), res.end());
    for(int i=0;i<res.size();i++)
         cout<<res[i]<<" ";
​
​
}

第二题(经典最大矩阵和)

动态规划。

2.设计步骤:

问题从一维变成了二维,但实质是一样的,同样是再求最大子序和,我们需要将二维转化为一维,对于矩阵的每一列,我们将其加在一起,成为了一维上的一个数,二维矩阵的和转化为了一维数组的和。

image.png

//
// Created by 86191 on 2022-09-30.
//#include <iostream>
#include <algorithm>
#include <vector>
using namespace  std;
int n,m;
int a[100][100];
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>a[i][j];
    vector<int> ans(4);
    int N = n;
    int M = m;
    int b[m+1];
    int sum;
    int maxsum=-1e9;
    int bestr1,bestc1;
cout<<"-------------"<<endl;
    for(int i=0;i<N;i++){
        for(int t=0;t<M;t++ ) b[t]=0;
        for(int j=i;j<N;j++){    //子矩阵的下边,从i到N-1,不断增加子矩阵的高
            //一下就相当于求一次最大子序列和
            sum = 0;//从头开始求dp
            for(int k=0;k<M;k++){
                b[k]+=a[j][k];
//                cout<<b[k]<<endl;
                if(sum>0){
                    sum+=b[k];
                }
                else{
                    sum=b[k];
                    bestr1=i;//自立门户,暂时保存其左上角
                    bestc1=k;
                }
                if( sum > maxsum){
                    maxsum = sum;
                    ans[0]=bestr1;//更新答案
                    ans[1]=bestc1;
                    ans[2]=j;
                    ans[3]=k;
                }
            }
        }
    }
cout<< maxsum<<endl;
    cout<<"子矩阵左上角坐标:("<<ans[0]<<","<<ans[1]<<")"<<endl;
    cout<<"子矩阵右上角坐标:("<<ans[2]<<","<<ans[3]<<")"<<endl;
}

动态规划的题大多数可以先画一个图,根据图片来理解查找漏洞会好很多。