学习笔记:动态规划

学习笔记:动态规划

∙ \bullet 先来看一个问题:
小张现在有8个任务可选,每个任务都必须在规定的时间段完成不能多也不能少,而且每个任务都有对应的报酬如下图,问小张应如何选择才能拿到最多的报酬?
在这里插入图片描述首先试试贪心能不能解决,怎么贪心呢?优先选报酬夺的?还是优先选时间短的?我们不妨来试试。优先选报酬多的,那么他就会选任务3和任务8,能得到12元;优先选时间短的,那么他就有很多选法,是不是报酬最多的也要看运气,所以贪心并不能解决这个问题。

∙ \bullet 那我们换种方法来解决这个问题吧,
首先,每个任务都有选和不选两种选择,我们从最后一个任务开始模拟这个过程。首先我们需要先用一个数组 p r e pre pre[ ],那么 p r e [ i ] pre[i] pre[i]表示在所有编号小于 i i i的任务中,编号最接 i i i近且时间段与 i i i紧密相连(就是做完一个任务紧接着做下一个任务)的任务编号。那么这个数组就可以表示为下表:

i i i12345678
p r e [ i ] pre[i] pre[i]00010235

开始模拟选与不选的过程:首先我们用一个 o p t opt opt[ ]数组, o p t [ i ] opt[i] opt[i]表示第 i i i个任务能获得的报酬的最优解(此最优解不表示第 i i i个任务的报酬)

第8个任务有两种选择:选第8个任务,那么 o p t [ 8 ] = o p t [ p r e [ 8 ] ] + 任 务 8 报 酬 opt[8]=opt[pre[8]]+任务8报酬 opt[8]=opt[pre[8]]+8,它表示如果选了第8个任务,也选了之前的某个任务,那么之前的某个任务就不能与第8个任务有时间冲突,所以这种情况下的第8个任务的报酬就是选了的之前某个任务的最优报酬+第8个任务的报酬;不选第8个任务,那么第8个任务的最优报酬就等于第7个任务最优报酬。为了便于理解,我画了一个图:
在这里插入图片描述假设数组 g a i n [ i ] gain[i] gain[i]表示第 i i i个任务的最优报酬,那么上述过程就可以简化为下面这个方程:
o p t [ i ] = m a x { o p t [ p r e [ i ] ] + g a i n [ i ] , 选 o p t [ i − 1 ] , 不选 opt[i] =max \begin{cases} opt[pre[i]]+gain[i], & \text{选} \\ opt[i-1], & \text{不选} \end{cases} opt[i]=max{opt[pre[i]]+gain[i],opt[i1],不选此过程可递归实现,注意递归出口: o p t [ 0 ] = 0 , o p t [ 1 ] = g a i n [ 1 ] opt[0]=0,opt[1]=gain[1] opt[0]=0,opt[1]=gain[1].

如果数据足够大的话,递归就不行了,我们再来看上面的那幅图:
在这里插入图片描述注意到递归重复算了 o p t [ 5 ] , o p t [ 3 ] opt[5],opt[3] opt[5],opt[3],这无疑是对时间和空间的浪费,这种问题叫做重叠子问题,我们已经递归得算出来了 o p t [ 5 ] opt[5] opt[5] o p t [ 3 ] opt[3] opt[3],那为何不存储下来,如果以后还要用到它们就可以直接取出来用就行了,这种方法就叫做记忆化存储。那我们就想办法优化一下,这次我们不从最后一个开始选了,我们就从第一个任务开始选,于是用一个循环就能解决问题(循环过程记忆化存储 o p t [ i ] opt[i] opt[i]),时间复杂度为 O ( n ) O(n) O(n)。状态转移方程如下:
o p t [ i ] = { m a x ( o p t [ p r e [ i ] ] + g a i n [ i ] , o p t [ i − 1 ] ) i > 1 g a i n [ 1 ] i = 1 0 i = 0 opt[i] = \begin{cases} max(opt[pre[i]]+gain[i],opt[i-1] )&i>1 \\ gain[1]&i=1\\ 0&i=0 \end{cases} opt[i]=max(opt[pre[i]]+gain[i],opt[i1])gain[1]0i>1i=1i=0
∙ \bullet 最后再来说一说动态规划题目得特点即基本思想:对于一个给定的问题,这个问题可以分解为若干性质相同或相似的子问题,且每个子问题都有其最优解的解决方法,那么这个大问题的最优解就可以由若干个子问题的最优解结合得到。对于上述问题,我之所以要先从最后一个任务来推,就是为了发现,这个大问题的最优解可以由它的子问题的最优解来得到,并且还发现了重叠子问题,这时就需要记忆化存储,一旦某一个子问题的最优解已解出,就需要将其记忆化存储下来,以便下次再遇到同一个子问题就可以直接查表,从而减少时间和空间复杂度。所以动态规划常用于有重叠子问题和最优子结构的问题中。 当然动态规划不常用递归来做,一旦求出了状态转移方程就可以用迭代来做。

∙ \bullet 如何发现一个问题是否能用动态规划来做呢,首先要明白问题的最优解结构,以及子问题的最优解结构,如果它们的最优解结构相似或者相同的话,递归的模拟一下由大问题转换为子问题的过程,看是否有重叠子问题以及递归出口等。最后再总结出来状态转移方程就好了。

之后再补上例题。。。。。

∙ \bullet 来个简单的例题:
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
在这里插入图片描述
有如图所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

Input

输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

Output

对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input

1
5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output

30

状态转移方程:

#include <stdio.h>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=105;
int mp[maxn][maxn];

int main(){
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=i;j++){
                scanf("%d",&mp[i][j]);
            }
        }
        for(int i=n-1;i>=0;i--){
            for(int j=1;j<=i;j++)
            mp[i][j]+=max(mp[i+1][j],mp[i+1][j+1]);
        }
        printf("%d\n",mp[1][1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值