学习笔记:动态规划

学习笔记:动态规划

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

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

i i 1 2 3 4 5 6 7 8
p r e [ i ] pre[i] 0 0 0 1 0 2 3 5

开始模拟选与不选的过程:首先我们用一个 o p t opt [ ]数组, o p t [ i ] opt[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报酬 ,它表示如果选了第8个任务,也选了之前的某个任务,那么之前的某个任务就不能与第8个任务有时间冲突,所以这种情况下的第8个任务的报酬就是选了的之前某个任务的最优报酬+第8个任务的报酬;不选第8个任务,那么第8个任务的最优报酬就等于第7个任务最优报酬。为了便于理解,我画了一个图:
在这里插入图片描述假设数组 g a i n [ i ] gain[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} 此过程可递归实现,注意递归出口: o p t [ 0 ] = 0 , o p t [ 1 ] = g a i n [ 1 ] opt[0]=0,opt[1]=gain[1] .

如果数据足够大的话,递归就不行了,我们再来看上面的那幅图:
在这里插入图片描述注意到递归重复算了 o p t [ 5 ] , o p t [ 3 ] opt[5],opt[3] ,这无疑是对时间和空间的浪费,这种问题叫做重叠子问题,我们已经递归得算出来了 o p t [ 5 ] opt[5] o p t [ 3 ] opt[3] ,那为何不存储下来,如果以后还要用到它们就可以直接取出来用就行了,这种方法就叫做记忆化存储。那我们就想办法优化一下,这次我们不从最后一个开始选了,我们就从第一个任务开始选,于是用一个循环就能解决问题(循环过程记忆化存储 o p t [ i ] opt[i] ),时间复杂度为 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}
\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;
}
发布了34 篇原创文章 · 获赞 7 · 访问量 1900

猜你喜欢

转载自blog.csdn.net/qq_43628761/article/details/90739929