第五周总结

动态规划
今天总结一下动态规划,从以下5个部分完成这篇博客
一:什么是动态规划?
二:动态规划的特征
三:动态规划的几个概念
四:动态规划的基本墓型
五:解决动态规划问题的一般步骤
六:例题

一:什么是动态规划?
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principleof optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规
划。
动态规划是分阶段求最优值的算法。将复杂问题按阶段划分成子问题,然后枚举子问题各种可能情况,从中找出最优值,利用子问题的最优值求得源问题的最优解

二:动态规划问题的基本特征: 
 1.问题具有多阶段决策的特征。
 2.每一阶段都有相应的“状态”与之对应,描述状态的量称为“状态变量”。
 3.每一阶段都面临一个决策,选择不同的决策将会导致下一阶段不同的状态。
 4.每一阶段的最优解问题可以递归地归结为下一阶段各个可能状态的最优解问题,各子问题与原问题具有完全相同的结构。
 5.子问题一般通过数组形式描述它的状态,而元素的取值就是某种状态下的最优结果
 6.数组,不仅用来储存元素,还有含义,例如下面例题一中的f[MAX][MAX]是指走到这点获得的最大金币数

三:动态规划中的几个概念
阶段:据空间顺序或时间顺序对问题的求解划分阶段。
状态:描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画。对问题的求解状态的描述是分阶段的。
决策:根据题意要求,对每个阶段所做出的某种选择性操作。
状态转移方程:用数学公式描述与阶段相关的状态间的演变规律。

四:动态规划的基本模型
动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。
例如:
线性动规:最长上升子序列,最长公共子序列,拦截导弹,合唱队形等;
区域动规:石子合并,统计单词个数等;
树形动规:贪吃的九头龙,二分查找树,聚会的欢乐等;
背包问题:01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题等;
这些模型的具体例题会在未来学习中慢慢补充

五:解决动态规划问题的一般步骤
1、判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
2、把问题分成若干个子问题(分阶段)。
3、建立状态转移方程(递推公式)。
4、找出边界条件。
5、将已知边界值带入方程。
6、递推求解。

六:例题
例题1,吃金币游戏 (方格问题)
小明写了一个简单的吃金币游戏,规则如下:
在一个长方形地图上,玩家每次能从一个方格走到相邻一个方格。
玩家控制的角色可以向下或者向右走,但不能向上或向左走。每个方格上都有一定的金币。
现在,小明想请你帮他想一个策略,尽可能多的获得金币(从左上角走到右下角可能获得的最大金币数)。

其实通过这题直接就看出了dp与贪心的不同,它的每一步都需要一个决策,对于这题就是该往下走还是往右走
易得 F[a][b]=max(F[a-1][b],F[a][b-1])+Coin[a][b]
(这题我以为很简单,但刚开始我把二维数组开得很大,运行的时候不能输入,我也不知道咋回事,求解答)
 

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
    int i,j,money;
    int n,m;
    int f[101][101];
    cin>>m>>n;
    for(i=1;i<=m;i++)
           for(j=1;j<=n;j++)
    {
        cin>>money;
        f[i][j]=max(f[i-1][j],f[i][j-1])+money;
    }
    cout<<f[m][n]<<endl;
    return 0;
}

例题2
最长上升(不下降)子序列
输入数据
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N 个整数,这些整数的取值范围都在0 到10000。
输出要求
最长上升子序列的长度。
输入样例

7
1 7 3 5 9 4 8


输出样例

4


(1).从前往后,把每一个数都作为序列的最后一个数,对于b(1)来说,由于它是最后一个数,所以当从b(n)开始查找时,只存在长度为1的不下降子序列
(2).若从b(i)个数开始查找,则存在以下两种情况
a.b(i-1)<b(i),则存在长度为2的上升子序列b(i-1),b(i);
b.b(i-1)>b(i),则存在长度为1的上升子序列b(i-1)或b(i);

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
   int n,i,j,a[1001];
   int maxline[1001];//开始我是这样定义的int maxline[1001]={1};哈哈,我就因为这点改了好长时间,是不是很弱智
   cin>>n;
   for(i=1;i<=n;i++)
    cin>>a[i];
     maxline[1]=1;
   for(i=2;i<=n;i++)
    {
        int max=0;//记录第i个数左边子序列的最大长度
     for(j=1;j<i;j++)
     {
         if(a[i]>a[j])
         {
             if(max<maxline[j])
                max=maxline[j];
         }
     }
     maxline[i]=max+1;
    }
    int maxm=-1;
    for(i=1;i<=n;i++)
        {
         if(maxm<maxline[i])
        maxm=maxline[i];
        }
    cout<<maxm<<endl;
    return 0;
}

例题3:

最长公共子序列(LCS)
这题需要注意最长公共子序列与最长公共子串的区别,子串是串连续的一部分,子序列是不改变序列的顺序
例如:ABCBDAB与BDCABA的最长公共子序列长度是4(BCAB),注意并不一定连续
DP[i][j]代表  A串从起点到i位置的字串 和  B串从起点到j位置的字串 的LCS
当A[i] == B[j]  时 :DP[i][j] =  DP[i-1][j-1] + 1 
当A[i] != B[j]  时 : DP[i][j] = MAX(DP[i-1][j] , DP[i][j-1])

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<string>
using namespace std;
int dp[5001][5001];
string a,b;
int main()
{
    //int dp[1001][1001];
   //string a, b;
   cin>>a;
   cin>>b;
   int len1=a.length(),len2=b.length();
   for(int i=1;i<=len1;i++)
    for(int j=1;j<=len2;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[len1][len2];
    return 0;
}


例题四:
题目简介
描述:给定 N (1 <= N <= 100000) 个绝对值不大于 1000 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出连续一个子段,使这个子段的和最大。如果某子序列全是负数则定义该子段和为 0。求 max{0,a[i]+a[i+1]+…+a[j]}, 1 <= i <= j <= N
输入:有一个正整数 N,后面紧跟 N 个绝对值不大于 1000 的整数
输出:最大子段和
样例输入:

5
6 -1 5 4 -7


样例输出:

14


我觉得如果前面的懂了这题不成问题,思路几乎一样而且这题更容易想一些。当时看到有同学在群里讨论这道题说老师课件答案错了,应该是19,但我的运行结果就是14啊,我想可能大家没有输入N,直接6个数求最大了吧。
代码在此

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[10001],dp[10001];
int main()
{
    int i,n;
    cin>>n;
    for(i=1;i<=n;i++)
        cin>>a[i];
        dp[1]=max(a[1],0);
        for(i=2;i<=n;i++)
        {
    dp[i]=max(dp[i-1]+a[i],0);
        }
        int maxm=0;
        for(i=1;i<=n;i++)
            if(maxm<dp[i])
            maxm=dp[i];
        cout<<maxm<<endl;
    return 0;
}


例题5:
如果觉得例题4太简单了,那就让我们做改编的这道题吧
输出最大两个子段和
题目简介
描述:
给出n个整数: A={a1, a2,..., an}, 定义 d(A) 如下: 
 (这里题目是一个公式,可是公式我不会打哎,说一下意思吧。就是把原子段分成两段,求两段最大子段和的和)
要求计算d(A).
输入:第一行,整数n(2<=n<=50000). 第二行包含n个整数 a1, a2, ..., an. (|ai| <= 10000)
输出:每组测试数据输出一行,输出d(A)的值. 
样例输入:
10
1 -1 2 2 3 -3 4 -4 5 -5 
样例输出:
13 
错误代码

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[10001],dp[10001],dpp[10001];
int main()
{
    int i,n,t;
    cin>>n;
    for(i=1;i<=n;i++)
        cin>>a[i];
        dp[1]=max(a[1],0);
        for(i=2;i<=n;i++)
        {
    dp[i]=max(dp[i-1]+a[i],0);
        }
        int maxm=0;
        for(i=1;i<=n;i++)
            if(maxm<dp[i])
            {maxm=dp[i];
            t=i;}
            //cout<<t<<endl;
           // cout<<maxm<<endl;
            dpp[t]=max(a[t+1],0);
            for(i=t+1;i<=n;i++)
                dpp[i]=max(dpp[i-1]+a[i],0);
                int maxmm=0;
        for(i=t+1;i<=n;i++)
            if(maxmm<dpp[i])
            maxmm=dpp[i];
           // cout<<maxmm<<endl;
            cout<<maxm+maxmm<<endl;
}


不知道有没有和我一样图省事直接改上一题的代码的,这样做可能只能保证前面是整个子段中最大的,但不能保证两段和是子段中最大的
正确代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int a[10001],f[10001],b[10001];//定义前后两个子段和
int main()
{
    int i,n,sum=0;
    cin>>n;
    f[0]=-10001;
    for(i=1;i<=n;i++)
    {
         cin>>a[i];
         if(f[i-1]>a[i]+sum)
            f[i]=f[i-1];
         else
            f[i]=a[i]+sum;
            sum+=a[i];
         if(sum<0)
            sum=0;
    }
    b[n+1]=-10001;
    sum=0;
    int max=-10001;
    for(int i=n;i!=0;i--)//从后向前找第二个子段和
    {
        if(b[i+1]>a[i]+sum)
            b[i]=b[i+1];
        else
            b[i]=a[i]+sum;
        sum+=a[i];
        if(sum<0)
            sum=0;
        //枚举最大两段的子段和
        if(f[i]+b[i+1]>max)
            max=f[i]+b[i];
    }
 cout<<max<<endl;
return 0;
}


例题6:
最大矩阵和
题目简介
描述:给定 N (1 <= N <= 100) 和一个 N * N 的矩阵,求该矩阵的一个子矩阵,使得子矩阵中所有数字的和最大。求出这个最大值。
输入:一个整数 N,然后是 N * N 个绝对值不超过 128 的整数
输出:只有一个值,最大子矩阵和
样例输入:
4
0 -2 -7 0 9 2 -6 2
-4 1 -4 1 -1
8 0 -2
样例输出:
15
这题相对于前面两个难度进一步增加了
对于一个n维矩阵,这里采取压缩成一维矩阵的方法,想象一下把所有矩阵的列数全部加到第一列,那么此刻矩阵就是n*1的一个列矩阵了
当然了,要求最大矩阵和列也要采取枚举的方式逐列加(这时候可以把列看作一个列矩阵,采取上题求最大子段和的方法了);同时采取枚举的方式逐行扩大矩阵

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 101
int fun(int b[N],int n)
{
    int i,max,c;
    c=0;max=0;
    for(i=1;i<=n;i++)
    {
        if(c>0)
            c=c+b[i];
        else c=b[i];
        if(max<c)
            max=c;
    }
    return max;
}
int main()
{
   int a[N][N],b[N];//定义矩阵 a数组用来存放每一列的和
   int i,j,n,max,sum;
   cin>>n;
   for(i=1;i<=n;i++)
    for(j=1;j<=n;j++)
    cin>>a[i][j];
   max=0;
   for(i=1;i<=n;i++)//首行
    {
        for(j=1;j<=n;j++)//最后一行赋初值
        b[j]=0;
        {
            for(j=i;j<=n;j++)//最后一行
            {
                for(int k=1;k<=n;k++)//列
                b[k]+=a[j][k];
                sum=fun(b,n);
                if(max<sum)
                    max=sum;
            }
        }
    }
        cout<<max<<endl;
return 0;
}

例题7:

Given a set of n integers: A={a1, a2,..., an}, we define a function d(A) as below:


Your task is to calculate d(A).

Input

The input consists of T(<=30) test cases. The number of test cases (T) is given in the first line of the input.
Each test case contains two lines. The first line is an integer n(2<=n<=50000). The second line contains n integers: a1, a2, ..., an. (|ai| <= 10000).There is an empty line after each case.

Output

Print exactly one line for each test case. The line should contain the integer d(A).

Sample Input

1

10
1 -1 2 2 3 -3 4 -4 5 -5
Sample Output

13
Hint

In the sample, we choose {2,2,3,-3,4} and {5}, then we can get the answer.

Huge input,scanf is recommended.


原文链接:https://blog.csdn.net/weixin_45901806/article/details/105117220

哎,这题在我上篇博客已经发过了,本来打算今天用一上午的时间把老师这两节课讲得所有题目整理一遍,然后下午做两道题就准备上课了,结果就因为这道题,我整整一上午没通过,郁闷死了。对了,这题中文翻译过来就是上面整理的例题5,不过用上面的答案是通过不了的,超时,正确答案代码在上面那篇代码。今天就这样吧,如果没有特殊原因,周五晚上我会整理剩下习题。老师说希望看到创新,希望多找类似的题去做,去整理。哎,我也很想啊,等到我把讲过的整理完之后再说吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

季沐晴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值