写这篇博文主要是为了归纳总结一下dp的有关问题(不定期更新,暑假应该会更的快一些)

会大概讲一下思路,不会事无巨细地讲

另一篇是平时做过的一些dp题,这篇博客里面提到的题都有题解放在那边:https://www.cnblogs.com/henry-1202/p/9211398.html

这个玩意更新会有点慢,比较系统的学过一些dp的问题之后才会来写这个(可能要有人来催更才会写?)


一.最长上升子序列问题(LIS)

大概意思是给一个序列,按从左到右的顺序选出尽可能多的数,组成一个上升子序列(子序列:对于一个序列,在其中删除1个或多个数,其他数的顺序不变)

随便来个序列:1 8 7 4 8 9

它的最长上升子序列是1 4 8 9(当然也可以是1 7 8 9)

对于这个问题,有O(n2)做法和O(nlogn)做法,这里两个做法都会讲到

1.LIS的O(n2)做法

对于一个简单的LIS,你是怎么用肉眼判断的?一个一个往后找,然后数出最长的LIS,是不是?

n2做法就是用这种思路来做的,事实上这个做法就是一个优化后的暴力

设f[i]为以i为结尾的LIS的最大长度(也可以是起点),则有

f[i]=max(f[i],f[j]+1)(a[i]>a[j])

代码如下:

for(int i=;i<=n;i++){
f[i]=;//这里不要忘记初始化,这是每个人都很容易忘记的地方
for(int j=;j<i;j++){
if(a[i]>a[j])f[i]=max(f[i],f[j]+);
}
}

当然第二层循环也可以枚举j+1到n,都一样的,if里面的大于号改一下就好(这样的话就是f数组表示以ai为起点的最长上升子序列的)

这五行代码是LIS的基本模型,基本所有LIS的题都是对这五行代码添添改改搞出来的,所以在学习下面的东西之前需要务必确保自己完全搞懂这五行代码了再继续看

确保自己搞懂了之后可以先做一下例题中的合唱队形还有导弹拦截的n2做法

2.LIS的O(nlogn)做法

在学习LIS的nlogn做法之前,你需要两个前置姿势:知道什么是贪心,并且会打二分查找

想想LIS:在一个序列中选出尽可能多的数组成一个上升子序列。

让我们用用贪心的思想:

对于n2做法,我们是在求出以ai为结尾(起点)的LIS,以不断更新f数组的值的方式来维护LIS,而在这个过程中,f数组的值一定是最优的,而对于LIS来说,最优意味着什么?

对于最优的LIS,它的最后一位一定是尽可能小的,因为这样在后面更新的过程中,你才有更大的可能性让这个LIS变得更长

LIS的nlogn做法就是根据这种思想来做的,然后通过二分查找来使效率降到nlogn

相应的,f数组的含义也需要改一下:fi代表长度为i的LIS的最后一位数

在看代码之前请确保你理解了上面的贪心的思路

int m=;//m是指LIS的长度,m=1是因为LIS的长度至少为1
memset(f,,sizeof(f));//初始化,这里的127表示的是无穷大
f[]=a[];//初始化
for(int i=;i<=n;i++){
if(a[i]>f[m])f[++m]=a[i];//如果比当前的LIS的最后一位还大那这个LIS就可以变长了
else {//通过二分查找来更新f数组
int l=,r=m;
while(l<r){
int mid=(l+r)>>;
if(a[i]>f[mid])r=mid;
else l=mid+;
}
f[l]=a[i];//不要忘记更新
}
}

想练一下LIS的nlogn做法的话就去试试洛谷的导弹拦截吧,可以去我的另一篇博客看题解qwq,链接在上面


二,最长公共子序列(LCS)

公共子序列是指:一个同时是这两个序列的子序列的序列

有点绕口...反正就是那个意思

例如:

1 2 3 4 5

2 1 3 5 4

LCS的长度显然是3(1,3,4/2,3,4)

于是可以设f[i][j]表示第一个序列的前i位与第二个序列的前j位的LCS

答案就是f[len1][len2]

转移方程其实很好想的:

如果第一个序列的i位与第二个序列的j位相同,那么转移:f[i][j]=max(f[i-1][j],f[i][j-1])+1

如果不同那么考虑继承之前的最优答案:f[i][j]=max(f[i-1][j],f[i][j-1])

这里给例题中的 洛谷P1439[模板]最长公共子序列 的朴素LCS做法

#define ll int
#define N 1010
ll n,a[N],b[N],f[N][N];
int main(){
scanf("%d",&n);
for(ll i=;i<=n;i++)scanf("%d",&a[i]);
for(ll i=;i<=n;i++)scanf("%d",&b[i]);
for(ll i=;i<=n;i++){
for(ll j=;j<=n;j++){
if(a[i]==b[j])f[i][j]=max(f[i-][j],f[i][j-])+;
else f[i][j]=max(f[i-][j],f[i][j-]);
}
}
printf("%d",f[n][n]);
return ;
}

然后这道题的100%做法是很妙的,有兴趣的话可以去做一下(上面的做法只能50分)


三.区间dp

这个我应该会写多一点……

区间dp的定义:顾名思义,就是在对区间进行dp,一般是对小区间进行dp得出最优解,然后通过小区间的最优解得出一整个区间的最优解

大概是这样?反正也差不多,在没有题的情况下再怎么讲也很空泛,上面的定义随便看看就好,知道区间dp是个什么东西就行,具体看下面的例题来理解。

1.矩阵链乘问题

这是紫书和算导都有的东西,不过我没有在OJ上面找到这道题...

首先在讲这道题之前我们要明确矩阵乘法的几个概念

1.两个矩阵能够相乘,当且仅当矩阵A的列数等于矩阵B的行数

2.设矩阵A的规模为n*p,矩阵B的规模为p*m,则这两个矩阵相乘的运算量为n*m*p

2.矩阵乘法满足结合律但不满足分配律

在明白矩阵乘法的概念之后,就可以来看这道题了

题意:给n个矩阵,全部都要乘起来,最后得到一个矩阵,你可以给他们加括号改变运算顺序,求最少的运算量,假设第i个矩阵Ai的规模是pi-1*pi

显然,加不加括号对这道题的运算量的影响是巨大的,举个例子,对于三个矩阵A,B,C,假设他们分别是2*3,3*4,4*5的,那么(A*B)*C的运算量为64,A*(B*C)的运算量为90,,这两种运算方式得出的最终的矩阵其实是一样的,但是运算量差别就很大了。

那么怎么解决这道题?

我们不妨用一般解dp题的思路来看看,把这一整个序列分成多个小的序列(划分子问题)

设l为一个小序列的左端点,r为一个小序列的右端点,那么当这个小序列足够小(l==r),运算量显然为0(你没办法用它自己来乘它自己),这样我们就能得出初始化的情况了

然后假设我们现在正在求解某个子问题,这一次乘法是第k个乘号,那么我们一定已经算出A1*A2*···*Ak和Ak+1*Ak+2*···*An的最优结果,因此对于这一次相乘的结果它一定也是最优的(dp的最优子结构性质)

设我们正在求解的这个子问题的运算量为f[i][j],那么可得f[i][j]=min{f[i][k]+f[k+1][j]+pi-1*pj*pk}(k即上文我们提到的“最优的第k个乘号”)

那么有了转移方程后我们就可以来写这道题了吗?不,我们还需要注意到一个特殊的情况,如果采用递推求解的方式,因为需要满足dp的无后效性的性质,所以我们不能按照i/j的递增/递减顺序来进行运算,而是要以j->i递增的顺序来运算。

当然记忆化搜索就没有这个问题了,不过我不习惯打记忆化搜索,这一方面的话算导有讲到

总的时间复杂度为O(n3)

是的没有代码我懒得写,不过反正就是一个引入,应该不需要代码吧···理解一下思路

真的需要代码的话可以跟我讲下我去搞一下

二.石子合并问题

题目链接

区间dp的例题,流传甚广的一道题,这里只给O(n3)做法,四边形不等式优化我不会啊>_<

与上一题相同,枚举断点划分子问题,通过子问题的最优解得出整个区间的最优解,区间dp的题都是这种套路

显然对于每个区间f[i][j],一定是由最优断点的结果转移过来的(不难证明:如果当前断点不是最优的,那么如果使用更优的断点得出的结果,也一定比当前断点更优)

于是设f[i][j]表示把i~j这个区间的石子合并为一堆石子的最小/最大花费

答案就是所有区间长度为n的区间的最大值/最小值

然后代码中的具体实现,我个人推荐的做法是枚举区间长度,左端点和断点,右端点可以通过左端点和区间长度的值算出来

还有就是对于石子的个数处理一下前缀和存在数组c中,这样在转移的时候就可以O(1)得到i~j堆石子的合并花费

转移方程即为f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+c[j]-c[i-1])(对于最小值是min)

#include <cstdio>
#include <cstring>
#define ll long long
#define inf 1<<30
#define il inline
#define in1(a) read(a)
#define in2(a,b) in1(a),in1(b)
#define in3(a,b,c) in2(a,b),in1(c)
#define in4(a,b,c,d) in2(a,b),in2(c,d)
il int max(int x,int y){return x>y?x:y;}
il int min(int x,int y){return x<y?x:y;}
il int abs(int x){return x>?x:-x;}
il void swap(int &x,int &y){int t=x;x=y;y=t;}
il void readl(ll &x){
x=;ll f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-f;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
x*=f;
}
il void read(int &x){
x=;int f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-f;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
x*=f;
}
using namespace std;
/*===================Header Template=====================*/
#define N 210
int n,f_min[N][N],f_max[N][N],a[N],c[N];
int main(){
in1(n);
for(int i=;i<=n;i++)in1(a[i]),a[i+n]=a[i];
for(int i=;i<=*n;i++)c[i]=c[i-]+a[i];
for(int len=;len<=n;len++){
for(int i=;i<=*n;i++){
int j=i+len-;f_min[i][j]=inf;
for(int k=i;k<j;k++){
f_min[i][j]=min(f_min[i][j],f_min[i][k]+f_min[k+][j]+c[j]-c[i-]);
f_max[i][j]=max(f_max[i][j],f_max[i][k]+f_max[k+][j]+c[j]-c[i-]);
}
}
}
int ans_max=,ans_min=inf;
for(int i=;i<=n;i++){
ans_max=max(ans_max,f_max[i][i+n-]);
ans_min=min(ans_min,f_min[i][i+n-]);
}
printf("%d\n%d\n",ans_min,ans_max);
return ;
}

石子合并


四.最大子段和问题

挺简单的一类dp问题

最大子段和问题是这样的:

给你一个序列,正负不定,你需要求出$a[i]+a[i+1]+···+a[j-1]+a[j]$的最大值$(1<=i<=j<=n)$

分治和dp的经典题

这里只讲dp做法

我们设$f[i]$表示从1到i的最大子段和,那么可以很简单的推出转移方程

$f[i]=max(f[i-1]+a[i],a[i])$

也可以换一种写法

if(f[i-1]>0)f[i]=f[i-1]+a[i];

else f[i]=a[i];

初始化f[1]=a[1]


以上就是最基础的几个dp类型啦(完结撒花)

入门dp总结的更多相关文章

  1. HDU 1231 最大连续子序列 --- 入门DP

    HDU 1231 题目大意以及解题思路见: HDU 1003题解,此题和HDU 1003只是记录的信息不同,处理完全相同. /* HDU 1231 最大连续子序列 --- 入门DP */ #inclu ...

  2. HDU 2571 命运 (入门dp)

    题目链接 题意:二维矩阵,左上角为起点,右下角为终点,如果当前格子是(x,y),下一步可以是(x+1,y),(x,y+1)或者(x,y*k) ,其中k>1.问最大路径和. 题解:入门dp,注意负 ...

  3. 【笔记】入门DP

    复习一下近期练习的入门 \(DP\) .巨佬勿喷.\(qwq\) 重新写一遍练手,加深理解. 代码已经处理,虽然很明显,但请勿未理解就贺 \(qwq\) 0X00 P1057 [NOIP2008 普及 ...

  4. UVA 674 (入门DP, 14.07.09)

     Coin Change  Suppose there are 5 types of coins: 50-cent, 25-cent, 10-cent, 5-cent, and 1-cent. We ...

  5. 跟着大佬重新入门DP

    数列两段的最大字段和 POJ2479 Maximum sum Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 41231 Acce ...

  6. Dropping water balloons (入门dp)

    2017-08-12 18:36:24 writer:pprp 最近刚刚接触动态规划,感觉状态的查找和转移自己很难想到,都是面向题解编程,但是一开始都是这样了,只有相信我可以独立自己解决动态规划这类问 ...

  7. 01背包入门 dp

    题目引入: 有n个重量和价值分别为Wi,Vi的物品.从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中的价值总和的最大值. 分析: 首先,我们用最普通的方法,针对每个物品是否放入背包进行搜索. ...

  8. NOJ——1568走走走走走啊走(超级入门DP)

    [1568] 走走走走走啊走 时间限制: 1000 ms 内存限制: 65535 K 问题描述 菜菜赚了钱回来,想起要买很多桶回来,不同地方的桶质量是不同的,他在(1,1)点出发因为飞机票有点贵所以他 ...

  9. UVA 11584 入门DP

    一开始把它当成暴力来做了,即,从终点开始,枚举其最长的回文串,一旦是最长的,马上就ans++,再计算另外的部分...结果WA了 事实证明就是一个简单DP,算出两个两个点组成的线段是否为回文,再用LCS ...

随机推荐

  1. Spring-事务管理(Transaction)

    1.事务介绍 事务(Transaction):访问并能更新数据库中数据项的一个程序执行单元. 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须要么全部完成,要么什么都不做,如果有 ...

  2. SQL Server 镜像证书过期处理

    转自:https://www.cnblogs.com/trams/archive/2012/01/13/2321637.html SQL Server 镜像证书过期处理 今天镜像中的主服务器进行维护重 ...

  3. 理解SQL Server中索引的概念,原理

    转自:http://www.cnblogs.com/CareySon/archive/2011/12/22/2297568.html 简介 在SQL Server中,索引是一种增强式的存在,这意味着, ...

  4. python3与mysql:创建表、插入数据54

    import pymysql db = pymysql.connect(host=',db='jodb1',port=3307,charset='utf8') # #测试连接开发库成功 # db = ...

  5. python中 staticmethod与classmethod

    原文地址https://blog.csdn.net/youngbit007/article/details/68957848 原文地址https://blog.csdn.net/weixin_3565 ...

  6. Python 之 os.walk()

    原文地址https://www.cnblogs.com/JetpropelledSnake/p/8982495.html          http://www.runoob.com/python/o ...

  7. 怎么在jquery里清空文本框的内容

    $("input[name='test']").val("").focus(); // 将name=test的文本框清空并获得焦点,以便重新输入

  8. OpenCV Mat数据类型及位数总结(转载)

    OpenCV Mat数据类型及位数总结(转载) 前言 opencv中很多数据结构为了达到內存使用的最优化,通常都会用它最小上限的空间来分配变量,有的数据结构也会因为图像文件格式的关系而给予适当的变量, ...

  9. php中in_array使用注意

    可能会导致长耗时: http://www.jb51.net/article/41446.htm

  10. Yii2 配置 Nginx 伪静态

    主要检查以下代码: location / { # Redirect everything that isn't a real file to index.php try_files $uri $uri ...