浅谈个人学DP的经历和感受

动态规划的定义!

首先,我们看一下官方定义:
定义:
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
————————————————
版权声明:本文为CSDN博主「BS有前途」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ailaojie/article/details/83014821

不会也不要急,每个人刚学动态规划的时候都很懵逼!!!

我第一次接触动态规划大概是额,一个月之前了吧,当时老师完全给我讲懵了。然后课听完了脑子一片空白。

然后又请教@SXY大佬开小灶,总算讲明白了一点,但是我还是害怕呀!因为我根本就不熟,所以一个月之内基本没怎么做。

直到今天我觉得是时候解决这个问题了,于是我打开了(小破谷)伟大的洛谷,找到了动态规划题单,冒着满头大汉开始了征程……

个人的理解

上面第一段是动态规划的基本定义(废话),本蒟蒻的理解是动态规划分为两种。

第一种是递推(之前的博文已经讲过),

第二种是化解大问题为小问题,求小问题的最优解,以达到全局最优解。

与贪心不同的是,动态规划并不是求得每步的最优解就能达到全局最优解,而是从整体来看,选择取舍,这也直接导致他比贪心更加难以理解。(有种哲学意味?)

今天本蒟蒻主要谈谈第二种。

动态规划怎么做啊?

相信很多刚入c++这个大坑的同学都有这个疑问,该怎么“从大局考虑”呢?

我们先别急,看看定义。对于最简单、基础的动态规划(不是背包,背包本蒟蒻不会!),题目往往是问你:有一张图,每个点有一定的分值和通往其他点的路径,然后出题人会问你:怎么走可以使得总得分最多呢?

既然我们如果从头走的话,并不知道选择某条路径最后的得分情况,所以我们不能瞎选(贪心),那怎么办呢?

逆向思维!

如果我们不能从头走,那我们就从后往前走,从倒数第二个点开始枚举所有情况,求得该点之后所有路径的最优解,再枚举倒数第三个点的最优情况,把倒数第二个点的最优情况衔接上……

这样一直推到第一个点,就是最优解啦!!!

那么,用代码如何实现呢?

(敲黑板)

下面真的是重点了!!!(神犇、大佬请迅速撤离,以免窥探蒟蒻的世界!!!)

状态转移方程!!!

所谓状态转移方程(不是数学方程啊喂!)就是存储当前情况的状态的一个式子(废话)。

举个例子,有A、B、C、D四个城市,现在我们在A市,我去了C市然后又去了B市,最后到了D市。

那么我的状态就是A->C->B->D

在具体的动态规划中,我们用这个来存我们当前的最优解情况。

可能还没有听懂,没关系(因为我也不知道我BB了些啥),接下来我们上题目来实战演练!!!

实战演练!!!

我们先上一个简单的题!

ybt1281

一个数的序列bi,当b1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN)我们可以得到一些上升的子序列(ai1,ai2,...,aiK)这里1≤i1<i2<...<iK≤N比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列。

如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。

你的任务,就是对于给定的序列,求出最长上升子序列的长度。



求上升、下降子序列的题目是很经典的动态规划,也是最基础的,我们就拿这个题开刀!

题目很好理解,先上本蒟蒻代码

//1281
#include<iostream>
#include<algorithm>
using namespace std;
int QAQ[1001];//存储数列
int QWQ[1001];//状态数组!(核心)
int main()
{
    int n,maxn=0;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>QAQ[i];
        QWQ[i]=1;//所有数的初始长度为1(即以该数为开头和结尾的序列,这也是本题的状态)
    }
    for (int i=2;i<=n;i++)//从第二个开始枚举
    {
        for (int j=1;j<i;j++)//枚举到i,即在第i个数之前的所有数
        {
            if(QAQ[i]>QAQ[j])//如果你枚举的第j个数比第i个数小,那么以j为末尾的序列可以长度+1,并以i为末尾
            {
                QWQ[i]=max(QWQ[i],QWQ[j]+1);//看看是这个以这个数为结尾的子序列长,还是把它接到以QAQ[j]为结尾的子序列长,并取得最长的解,存储到这个数的QWQ(状态数组中)作为以这个数为结尾的最长子序列
            }//简单来说,当我们枚举到某一个数的时候,就找到一个能接在这个数前面的最长子序列,然后把这个数接在这个最长序列的后面,这个数的状态变为以它为结尾的最长子序列
        }
    }
    for (int i=1;i<=n;i++)
    {
        if (QWQ[i]>maxn)
        {
            maxn=QWQ[i];//找到状态数组所记录的最长子序列
        }
    }
    cout<<maxn;//输出他
    return 0;
}

我把这种方法叫做“枚举屁股,找一个最大的头”。

(本蒟蒻建议如果实在理解不了,就画图操作,我就是需要画图找状态转移方程的)

下一题

ybt1260、洛谷1020(升级版)

这个题算是一个最最最最经典的动态规划和贪心结合的题了。

先上题目

【题目描述】

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。



这个题和上面的大同小异,上面是求最长上升子序列,这个是求最长不下降子序列,所以只需要把上面的判定条件改成>,然后再写一个贪心就好了。

洛谷的题经过了改装,把数据范围扩大了,会TLE,所以需要二分查找,本蒟蒻不会,请各位大佬自行查阅题解。

上本蒟蒻代码

#include<iostream>
#include<algorithm>
using namespace std;
int QAQ[101000];
int QWQ[101000];
int high[101000];
int main()
{
    int i=0,maxn=0,m=0,flag=0,ans=1;
    while (cin>>QAQ[i])
    {
        QWQ[i]=1;
        i++;
    }
    for (int y=1;y<i;y++)
    {
        for (int j=0;j<y;j++)
        {
            if (QAQ[j]>=QAQ[y])
            {
                QWQ[y]=max(QWQ[j]+1,QWQ[y]);
            }
        }
    }
    for (int y=0;y<i;y++)
    {
        if (maxn<QWQ[y])
        {
            maxn=QWQ[y];
        }
    }
    cout<<maxn<<endl;
    for (int y=0;y<i;y++)
    {
        if (y==0)
        {
            high[y]=QAQ[y];
        }
        for (int j=0;j<i;j++)
        {
            if (high[j]>=QAQ[y])
            {
                high[j]=QAQ[y];
                flag=1;
                break;
            }
        }
        if (flag==0)
        {
            ans++;
            high[ans]=QAQ[y];
        }
        flag=0;
    }
    cout<<ans;
    return 0;
}

没啥特点,改判断符号就行了。

抬走下一位!

洛谷P1216

题目描述

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

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

在上面的样例中,从 7→3→8→7→5 的路径产生了最大

输入格式

第一个行一个正整数 r ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。


我们还是故技重施,枚举屁股,找一个最大的

上代码!

//P1216
#include<iostream>
#include<algorithm>
using namespace std;
int QAQ[1010][1010];//初始化数组
int main()
{
    int n;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=i;j++)
        {
            cin>>QAQ[i][j];//读入数据
        }
    }
    for (int i=n-1;i>=1;i--)
    {
        for (int j=1;j<=i;j++)
        {
            QAQ[i][j]+=max(QAQ[i+1][j],QAQ[i+1][j+1]);//状态转移方程,即从倒数第二行开始枚举,看看他往最后一行的哪个数走得到一个较大值,把这个较大值更新为这个位置的数值
        }//再枚举上一行,老方法,看看往下一行哪个数走得到较大值,更新这个位置的数值,由于这个题比较特殊(我也不知道特殊在哪)我没开状态数组,直接用的原数组存状态,但是安然无恙的AC了
    }
    cout<<QAQ[1][1];//枚举到第一行,第一个数的值即为最大值
    return 0;
} 

好了,重头戏终于来了,洛谷的一道黄题!(普及/提高-)

我今天花了一个半小时终于做出来了(本蒟蒻不容易啊)

洛谷P2196

题目描述

在一个地图上有N个地窖(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式

有若干行。

第1行只有一个数字,表示地窖的个数N。

第2行有N个数,分别表示每个地窖中的地雷个数。

第3行至第N+1行表示地窖之间的连接情况:

第3行有n-1个数(00或11),表示第一个地窖至第2个、第3个、…、第n个地窖有否路径连接。如第3行为1 1 0 0 0 … 0,则表示第1个地窖至第2个地窖有路径,至第3个地窖有路径,至第4个地窖、第5个、…、第n个地窖没有路径。

第4行有n-2个数,表示第二个地窖至第3个、第4个、…、第n个地窖有否路径连接。

… …

第n+1行有1个数,表示第n-1个地窖至第n个地窖有否路径连接。(为0表示没有路径,为1表示有路径)。

输出格式

有两行

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。


不多BB,上代码!!!

//P2196
#include<iostream>
#include<algorithm>
using namespace std;
int mapp[25][25];
int boom[25];
int QAQ[25];
int main()
{
    int n,maxn=0,st,l=0,ans;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>boom[i];
        QAQ[i]=boom[i];//初始化状态数组,即只在一个地窖中挖,能挖到的地雷数
    }
    for (int i=1;i<n;i++)
    {
        for (int j=i+1;j<=n;j++)
        {
            cin>>mapp[i][j];//初始化小地图,记录路径的通断
        }
    }
    for (int i=n;i>=1;i--)//从编号最大的地窖开始往前枚举
    {
        for (int j=i-1;j>=1;j--)
        {
            if (mapp[j][i]==1)//因为是倒着枚举的,所以把ij反过来
            {
                QAQ[j]=max(boom[j]+QAQ[i],QAQ[j]);//如果从第i个地窖过来,并挖完该地窖的地雷,可以得到比当前地窖状态(地雷数)更多的地雷,那么把这个地窖的状态更新为该地窖的地雷数+第i个地窖的地雷状态(第i个地窖之前的最优解)
            }
        }
    }
    for (int i=1;i<=n;i++)
    {
        if (QAQ[i]>maxn)
        {
            maxn=QAQ[i];//找到状态数组中最多的地雷数,输出他
            st=i;//记录能挖到最多地雷的地窖,把他当做开头
        }
    }
    cout<<st<<" ";输出开头
    for (int i=st;i<n;i++)//从开头开始枚举,因为之前的每一步状态都是最优解,所以状态数组中存的地雷数应该最大(注意不是原来的地雷数组(bomb),不然会变成贪心!),我们找到这个最大值,然后输出这个最大值的地窖编号。
    {
        for (int j=i;j<=n;j++)
        {
            if (mapp[i][j]==1&&QAQ[j]>l)//用l找出最大值
            {
                l=QAQ[j];
                ans=j;//ans即为答案
            }
        }
        if (l!=0)//因为丧心病狂的出题人会给你出几个宝藏地窖,就是单独的地窖,不能走,里面几百颗雷,所以要特判,因为只有当检测到路径时,l不为0,所以没有路径时不满足if条件,避免了乱输出。
        {
            l=0;
            i=ans-1;//第ans个地窖中有最多地雷,下一步从第ans个地窖向后枚举,因为回去的时候i++,所以这里-1
            cout<<ans<<" ";//输出编号
        }
    }
    cout<<endl<<maxn;
    return 0;
}

这个题比较好理解的方法是(小声BB):你可以把动态数组里的数理解为选择这条路,后面最多还能挖到多少雷。这样会好理解,后面的路径也要好找一些。

在一本通上还有另外一个题和这个题大同小异,这个是@SXY大佬教我记录路径的时候用的例题,可惜我把他教记录路径的方法的都忘了,555……

ybt1262

【题目描述】

在一个地图上有nn个地窖(n≤200),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径,并规定路径都是单向的,且保证都是小序号地窖指向大序号地窖,也不存在可以从一个地窖出发经过若干地窖后又回到原来地窖的路径。

某人可以从任意一处开始挖地雷,然后沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使他能挖到最多的地雷。

【输入】

第一行:地窖的个数;

第二行:为依次每个地窖地雷的个数;

下面若干行:

xiyi  //表示从xi可到yi,xi<yi。

最后一行为"00"表示结束。


不多BB,上大佬代码,由于太过高端,并且讲解年代久远,本蒟蒻无法给出注解,请各位大神自行理解。。。

//1262
#include<iostream>
#include<algorithm>
using namespace std;
int f[201][2],a[201],maxn,l;
bool mapp[201][201],flag;
int main()
{
    int n;
    cin>>n;
    int x,y;
    for (int i=1;i<=n;i++)
    {
        cin>>a[i];
        f[i][0]=a[i];
    }
    while(x!=0&&y!=0)
    {
        cin>>x>>y;
        mapp[x][y]=1;
    }
    for (int i=n-1;i>0;i--)
    {
        for (int j=n;j>i;j--)
        {
            if(mapp[i][j]==1&&a[i]+f[j][0]>f[i][0])
            {
                f[i][0]=a[i]+f[j][0];
                f[i][1]=j;
            }
        }
    }
    for (int i=1;i<=n;i++)
    {
        if(f[i][0]>maxn)
        {
            maxn=f[i][0];
            l=i;
        }
    }
    while (l!=0)
    {
        if (flag==1)
        {
            cout<<"-";
        }
        flag=1;
        cout<<l;
        l=f[l][1];
    }
    cout<<endl<<maxn;
    return 0;
}

额,今天的博客就水这么长吧,半夜了。本蒟蒻也要休息了。。。(话说我好像写了2个多小时???求各位看官的赞!)

原文地址:https://www.cnblogs.com/zaza-zt/p/12508258.html

时间: 2024-11-05 14:38:07

浅谈个人学DP的经历和感受的相关文章

ACM/ICPC算法训练 之 数学很重要-浅谈“排列计数” (DP题-POJ1037)

这一题是最近在看Coursera的<算法与设计>的公开课时看到的一道较难的DP例题,之所以写下来,一方面是因为DP的状态我想了很久才想明白,所以借此记录,另一方面是看到这一题有运用到 排列计数 的方法,虽然排列计数的思路简单,但却是算法中一个数学优化的点睛之笔. Poj1037  A decorative fence 题意:有K组数据(1~100),每组数据给出总木棒数N(1~20)和一个排列数C(64位整型范围内),N个木棒长度各异,按照以下条件排列,并将所有可能结果进行字典序排序 1.每一

浅谈双线程dp (nyoj61 nyoj712)经典【传字条】和【探 寻 宝 藏】

浅谈双线程dp 先看问题: 传纸条(一) 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描述 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了.幸运的是,他们可以通过传纸条来进行交流.纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n).从小渊传到小轩的纸条只可以向下或者向右传递,从小轩

浅谈测试rhel7新功能时的感受及遇到的问题

半夜起来看世界杯,没啥激情,但是又怕错误意大利和英格兰的比赛,就看了rhel7 相关新功能的介绍. 安装还算顺利,安装的界面比以前简洁的多,很清爽,分类很是明确. 有些奇怪的是,我安装的时候,怕有些基础的包没有装上去,所以选定了mini和Web的类型,结果还是有些基础的包没有安装,比如 ifconfig . 虚拟机的网卡,被识别为ens,有意思. yum groupinstall Base 这样的话,就可以把一些基础的包打上.可以正常的时候ifconfig lsof  . 这里需要说明的是,re

浅谈测试rhel7新功能时的感受及遇到的问题【转载】

半夜起来看世界杯,没啥激情,但是又怕错误意大利和英格兰的比赛,就看了rhel7 相关新功能的介绍. rhel7的下载地址: https://access.redhat.com/site/downloads/ 安装还算顺利,安装的界面比以前简洁的多,很清爽,分类很是明确. 有些奇怪的是,我安装的时候,怕有些基础的包没有装上去,所以选定了mini和Web的类型,结果还是有些基础的包没有安装,比如 ifconfig . 虚拟机的网卡,被识别为ens,有意思. yum groupinstall Base

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

浅谈数据库系统中的cache(转)

http://www.cnblogs.com/benshan/archive/2013/05/26/3099719.html 浅谈数据库系统中的cache(转) Cache和Buffer是两个不同的概念,简单的说,Cache是加速"读",而buffer是缓冲"写",前者解决读的问题,保存从磁盘上读出 的数据,后者是解决写的问题,保存即将要写入到磁盘上的数据.在很多情况下,这两个名词并没有严格区分,常常把读写混合类型称为buffer cache,本文后续的论述中,统一

浅谈算法和数据结构

: 一 栈和队列 http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html 最近晚上在家里看Algorithems,4th Edition,我买的英文版,觉得这本书写的比较浅显易懂,而且“图码并茂”,趁着这次机会打算好好学习做做笔记,这样也会印象深刻,这也是写这一系列文章的原因.另外普林斯顿大学在Coursera 上也有这本书同步的公开课,还有另外一门算法分析课,这门课程的作者也是这本书的作者,两门课都挺不错的. 计算

浅谈移动前端的最佳实践(转)

前言 这几天,第三轮全站优化结束,测试项目在2G首屏载入速度取得了一些优化成绩,对比下来有10s左右的差距: 这次优化工作结束后,已经是第三次大规模折腾公司框架了,这里将一些自己知道的移动端的建议提出来分享下,希望对各位有用 文中有误请您提出,以免误人自误 技术选型 单页or多页 spa(single page application)也就是我们常常说的web应用程序webapp,被认为是业内的发展趋势,主要有两个优点: ① 用户体验好 ② 可以更好的降低服务器压力 但是单页有几个致命的缺点:

浅谈跨国网络传输

在这个大数据,云部署不断映入眼帘的时代,也许很多人作为公司IT架构的管理者都会觉得有些无助和迷惘.新兴的科技确实给日常的IT工作带来了便利,但亦带来了种种挑战和不可预期的困难. 数据的存储,传输的便利固然重要,但是数据的安全却要重要的多.你永远都不会希望把自己的核心数据放到公共的存储空间中,也随即诞生了私有云等一系列的概念,但是终究还是第三方的架构方案,这种不可控性随时都可发生. 对于跨国的数据传输,国内的网络提供商无论是电信和联通都无法给出完美的答案,因为国内伟大的防火墙的原因,速度慢之又慢,