顾名思义,线性DP就是在一条线上进行DP,这里举一些典型的例子。

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

题目

给定一个长度为N的序列A,求最长的数值单调递增的子序列的长度。

上升子序列B可表示为B={Ak1,Ak2,···,Akp},其中k1<k2<···<kp

解析

状态:F[i]表示以A[i]为结尾的最长上升子序列的长度,边界为f[0]=0。

状态转移方程:F[i]=max{F[j]+1}(0≤j<i,A[j]<A[i])。

答案显然为max{F[i]}(1≤i≤N)。

事实上,无论是上升、下降还是不上升等等此类问题,代码都是相似的,唯一的区别只是判断的符号更改罢了。

Code

#include <iostream>
using namespace std;
int n,a[],f[],maxn;
int main()
{
f[]=;
cin>>n;
for(int i=;i<=n;i++)
{
cin>>a[i];
for(int j=;j<n;j++)
if(a[j]<a[i]) f[i]=max(f[i],f[j]+);
maxn=max(maxn,f[i]);
}
cout<<maxn;
return ;
}

LCS问题(最长公共子序列)

题目

给定两个长度分别为N、M的字符串A和B,求最长的既是A的子序列又是B的子序列的字符串的长度。

解析

状态:F[i][j]表示A的前i个字符与B的前j个字符中的最长公共子序列,边界为F[i][0]=F[0][j]=0。

状态转移方程:F[i][j]=max{F[i-1][j],F[i][j-1],F[i-1][j-1]+1(if A[i]=B[j])}。

答案为F[N][M]。

Code

#include <iostream>
using namespace std;
int n,m,f[][];
char a[],b[];
int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
{
cin>>a[i];
f[i][]=;
}
for(int i=;i<=m;i++)
{
cin>>b[i];
f[][i]=;
}
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
{
f[i][j]=max(f[i-][j],f[i][j-]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-][j-]+);
}
cout<<f[n][m];
return ;
}

数字三角形

题目

给定一个N行的三角矩阵A,其中第i行有i列,从左上角出发,每次可以向下方或向右下方走一步,最终到达底部。

求把经过的所有位置上的数加起来,和最大是多少。

解析

状态:F[i][j]表示走到第i行第j列,和最大是多少,边界为F[1][1]=A[1][1]。

状态转移方程:F[i][j]=A[i][j]+max{F[i-1][j],F[i-1][j-1](if j>1)}。

答案为max{F[N][j]}(1≤j≤N)。

Code

#include <iostream>
using namespace std;
int n,a[][],f[][],maxn;
int main()
{
cin>>n;
for(int i=;i<=n;i++)
for(int j=;j<=i;j++) cin>>a[i][j];
f[][]=a[][];
for(int i=;i<=n;i++)
for(int j=;j<=i;j++)
{
f[i][j]=a[i][j]+f[i-][j];
if(j>) f[i][j]=max(f[i][j],a[i][j]+f[i-][j-]);
}
for(int i=;i<=n;i++) maxn=max(maxn,f[n][i]);
cout<<maxn;
return ;
}

例题一:合唱队形

题目

【题目描述】

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1​,T2​,…,TK​, 则他们的身高满足T1​<...<Ti​>Ti+1​>…>TK​(1≤i≤K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

【输入格式】

共二行。

第一行是一个整数N(2≤N≤100),表示同学的总数。

第二行有n个整数,用空格分隔,第i个整数Ti​(130≤Ti​≤230)是第i位同学的身高(厘米)。

【输出格式】

一个整数,最少需要几位同学出列。

【输入样例】

       

【输出样例】

 

【数据规模】

对于50%的数据,保证有n≤20;

对于全部的数据,保证有n≤100。

解析

最少出列,就是最多留下。

分析一下队形,其实质便是先上升再下降,不难联想到最长上升子序列与最长下降子序列。

定义状态F[i][0/1],F[i][0]表示以第i个人为结尾的最长上升子序列,F[i][1]表示以第i个人为结尾的最长合唱队形,F数组初值都为1,。

所以前i个人的最长合唱队形为max(F[i][0],F[i][1])。

状态转移方程:

F[i][0]=max(F[i][0],F[j][0]+1(if T[i]>T[j]));(T[i]如题所述,j<i)

F[i][1]=max(F[i][1],a[j][0],a[j][1]+1(if T[i]<T[j]);

最后的答案为n-max(F[i][0],F[i][1])。

Code

#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
using namespace std;
int n,t[],f[][],ans;
int main()
{
cin>>n;
for(int i=;i<=n;i++) cin>>t[i];
for(int i=;i<=n;i++)
{
f[i][]=;
for(int j=;j<i;j++)
if(t[i]>t[j]) f[i][]=max(f[i][],f[j][]+);
}
for(int i=;i<=n;i++)
{
f[i][]=;
for(int j=;j<i;j++)
if(t[i]<t[j]) f[i][]=max(f[i][],max(f[j][],f[j][])+);
}
for(int i=;i<=n;i++) ans=max(ans,max(f[i][],f[i][]));
cout<<n-ans;
return ;
}

例题二:导弹拦截

题目

【题目描述】

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

输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

【输入格式】

1行,若干个整数。

【输出格式】

2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

【输入样例】

       

【输出样例】


【数据规模】

导弹高度是≤50000的正整数,导弹个数≤100000。

注:O(n2) 100分,O(nlogn) 200分。

解析

非常经典的一道线性DP题目。

先来说说O(n2)的做法:

第一问显然是在求最长不上升子序列,定义状态F[i]表示以第i个数为结尾的最长不下降子序列。

状态转移方程:F[i]=max(F[i],F[j]+1(if a[i]<=a[j]))(a[i]表示第i个导弹的高度,j<i)。

第二问实际上是在求最长上升子序列,证明比较麻烦,这里便不给出了,自行理解一下。

与第一问求法相同,只需要把<=改成>即可。

这种做法只有100分,考虑一下优化。


以第一问为例,观察样例,不难发现,当F的值相同时,越后面的导弹高度越高。

所以我们可以用一个d[i]维护F值为i的序列的最后一个导弹的值,t记录当前求出的最长不上升子序列的长度,

然后在递推时判断a[d[i]](1≤i≤t)的值,若大于等于当前导弹高度,就更新F。

第二问同理。

优化之后,虽然依旧是O(n2)的做法,但其时间复杂度却很小,足以拿到200分。


O(nlogn)的做法有很多,例如二分、线段树什么的,这里便不再给出(懒),有兴趣的可以自己尝试做做。

Code

#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
using namespace std;
int a[],f[],n,maxn=-;
int main()
{
while(scanf("%d",&a[++n])==) ;
n--;
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]+);
maxn=max(maxn,f[i]);
}
printf("%d\n",maxn);
maxn=-;
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]+);
maxn=max(maxn,f[i]);
}
printf("%d",maxn);
return ;
}

O(n^2) 100分做法

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
long long f[],a[],tot=,s=,d[],t=;
int main()
{
memset(d,,sizeof(d));
while(scanf("%lld",&a[++tot])==);
tot--;
for(int i=;i<=tot;i++)
{
f[i]=;
for(int j=t;j>=;j--)
if(a[i]<=a[d[j]])
f[i]=max(f[i],f[d[j]]+);
t=max(f[i],t);
d[f[i]]=i;
}
cout<<t<<endl;
t=;
for(int i=;i<=tot;i++)
{
f[i]=;
for(int j=t;j>=;j--)
if(a[i]>a[d[j]])
f[i]=max(f[i],f[d[j]]+);
t=max(f[i],t);
d[f[i]]=i;
}
cout<<t;
return ;
}

O(n^2)优化 200分做法

注:参考书籍:《算法竞赛进阶指南》

线性DP详解的更多相关文章

  1. 数位DP 详解

    序 天堂在左,战士向右 引言 数位DP在竞赛中的出现几率极低,但是如果不会数位DP,一旦考到就只能暴力骗分. 以下是数位DP详解,涉及到的例题有: [HDU2089]不要62 [HDU3652]B-n ...

  2. 动态规划晋级——HDU 3555 Bomb【数位DP详解】

    转载请注明出处:http://blog.csdn.net/a1dark 分析:初学数位DP完全搞不懂.很多时候都是自己花大量时间去找规律.记得上次网络赛有道数位DP.硬是找规律给A了.那时候完全不知数 ...

  3. 树形DP详解+题目

    关于树形dp 我觉得他和线性dp差不多 总结 最近写了好多树形dp+树形结构的题目,这些题目变化多样能与多种算法结合,但还是有好多规律可以找的. 先说总的规律吧! 一般来说树形dp在设状态转移方程时都 ...

  4. Canvas使用渐变之-线性渐变详解

    在canvas里面,除了使用纯色,我们还能把填充和笔触样式设置为渐变色:线性渐变和径向渐变. 线性渐变 createLinearGradient(x0,y0,x1,y1)  返回 CanvasGrad ...

  5. 数位DP详解

    算法使用范围 在一个区间里面求有多少个满足题目所给的约束条件的数,约束条件必须与数自身的属性有关 下面用kuangbin数位dp的题来介绍 例题  不要62 题意:在一个区间里面求出有多少个不含4和6 ...

  6. Android开发之线性布局详解(布局权重)

    布局权重 线性布局支持给个别的子视图设定权重,通过android:layout_weight属性.就一个视图在屏幕上占多大的空间而言,这个属性给其设 定了一个重要的值.一个大的权重值,允许它扩大到填充 ...

  7. 状压DP详解(位运算)

    前言: 状压DP是一种非常暴力的做法(有一些可以排除某些状态的除外),例如dp[S][v]中,S可以代表已经访问过的顶点的集合,v可以代表当前所在的顶点为v.S代表的就是一种状态(二进制表示),比如 ...

  8. 状态压缩dp 状压dp 详解

    说到状压dp,一般和二进制少不了关系(还常和博弈论结合起来考,这个坑我挖了还没填qwq),二进制是个好东西啊,所以二进制的各种运算是前置知识,不了解的话走下面链接进百度百科 https://baike ...

  9. 数位dp详解&&LG P2602 [ZJOI2010]数字计数

    数位dp,适用于解决一类求x~y之间有多少个符合要求的数或者其他. 例题 题目描述 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除 ...

随机推荐

  1. Dump文件的校验查看工具

    当我们抓取到Dump文件后,我们抓取的方式对不对,是否包含了我们想要的信息,可不可用,又或这个文件在抓取或传输过程种,有没有损坏,又或者我不想用Windbg进行细致的分析,只想大概了解下异常信息,在这 ...

  2. circus 进程以及socket 管理工具&&docker运行

    circus 是由mozilla 团队开发基于python 以及zeromq 的进程以及socket 管理的工具,类似supervisord 但是比supervisord 更灵活方便 来自官方的使用比 ...

  3. python 微服务开发书中几个方便的python框架

    python 微服务开发是一本讲python 如果进行微服务开发的实战类书籍,里面包含了几个很不错的python 模块,记录下,方便后期回顾学习 处理并发的模块 greenlet && ...

  4. PostGraphile 4.4 发布,支持real time 查询

    在4.4 之前,real time 是通过插件完成处理的,4.4 直接内置了,还是很方便的功能,总算 和其他类似graphql 平台看齐了,使用上还是挺方便的. 参考资料 https://www.gr ...

  5. Vue.set 向响应式对象中添加响应式属性,及设置数组元素触发视图更新

    一.为什么需要使用Vue.set? vue中不能检测到数组和对象的两种变化: 1.数组长度的变化 vm.arr.length = 4 2.数组通过索引值修改内容 vm.arr[1] = ‘aa’ Vu ...

  6. .bat批处理命令之设置关机倒计时脚本

    @ECHO off REM 不显示后续命令行及当前命令行 TITLE Shutdown countdown REM 设置脚本标题 COLOR 0A REM 设置脚本 背景色为黑色 前景色为淡绿色 :s ...

  7. 20189220 余超《Linux内核原理与分析》第五周作业

    扒开系统调用的三层皮?(上) 第4章的基础知识 Linux系统调用的三层机制:xyz()(API函数).system_call(系统调用处理入口) . sys_xyz()(系统调用内核处理函数). 3 ...

  8. 第06组 Alpha冲刺(4/6)

    队名:拾光组 组长博客链接 作业博客链接 团队项目情况 燃尽图(组内共享) 组长:宋奕 过去两天完成了哪些任务 主要完成了用户联系模块的接口设计 完善后端的信息处理 GitHub签入记录 接下来的计划 ...

  9. java.lang.Error: java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(Z)V

    有时候出现这种怪异的问题,是由于多个版本的class存在. 比如说:某个java编译成class后,放到classes下面,然后lib目录下,也有这个class所在的jar包,这样就导致classpa ...

  10. 利用art.template模仿VUE

    首先先看一下Typescript代码: import template = require('art-template/lib/template-web'); interface TemplateBi ...