线性DP详解
顾名思义,线性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详解的更多相关文章
- 数位DP 详解
序 天堂在左,战士向右 引言 数位DP在竞赛中的出现几率极低,但是如果不会数位DP,一旦考到就只能暴力骗分. 以下是数位DP详解,涉及到的例题有: [HDU2089]不要62 [HDU3652]B-n ...
- 动态规划晋级——HDU 3555 Bomb【数位DP详解】
转载请注明出处:http://blog.csdn.net/a1dark 分析:初学数位DP完全搞不懂.很多时候都是自己花大量时间去找规律.记得上次网络赛有道数位DP.硬是找规律给A了.那时候完全不知数 ...
- 树形DP详解+题目
关于树形dp 我觉得他和线性dp差不多 总结 最近写了好多树形dp+树形结构的题目,这些题目变化多样能与多种算法结合,但还是有好多规律可以找的. 先说总的规律吧! 一般来说树形dp在设状态转移方程时都 ...
- Canvas使用渐变之-线性渐变详解
在canvas里面,除了使用纯色,我们还能把填充和笔触样式设置为渐变色:线性渐变和径向渐变. 线性渐变 createLinearGradient(x0,y0,x1,y1) 返回 CanvasGrad ...
- 数位DP详解
算法使用范围 在一个区间里面求有多少个满足题目所给的约束条件的数,约束条件必须与数自身的属性有关 下面用kuangbin数位dp的题来介绍 例题 不要62 题意:在一个区间里面求出有多少个不含4和6 ...
- Android开发之线性布局详解(布局权重)
布局权重 线性布局支持给个别的子视图设定权重,通过android:layout_weight属性.就一个视图在屏幕上占多大的空间而言,这个属性给其设 定了一个重要的值.一个大的权重值,允许它扩大到填充 ...
- 状压DP详解(位运算)
前言: 状压DP是一种非常暴力的做法(有一些可以排除某些状态的除外),例如dp[S][v]中,S可以代表已经访问过的顶点的集合,v可以代表当前所在的顶点为v.S代表的就是一种状态(二进制表示),比如 ...
- 状态压缩dp 状压dp 详解
说到状压dp,一般和二进制少不了关系(还常和博弈论结合起来考,这个坑我挖了还没填qwq),二进制是个好东西啊,所以二进制的各种运算是前置知识,不了解的话走下面链接进百度百科 https://baike ...
- 数位dp详解&&LG P2602 [ZJOI2010]数字计数
数位dp,适用于解决一类求x~y之间有多少个符合要求的数或者其他. 例题 题目描述 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除 ...
随机推荐
- 在x64计算机上捕获32位进程的内存转储
这是一个我经常遇到的问题,我们经常会遇到这样的情况:我们必须重新捕获内存转储,因为内存转储是以“错误”的方式捕获的.简而言之:如果在64位计算机上执行32位进程,则需要使用允许创建32位转储的工具捕获 ...
- 2017.10.1 国庆清北 D1T1 zhx的字符串题
题目背景 2017国庆清北D1T1 题目描述 你是能看到第一题的 friends 呢. ——hja 何大爷对字符串十分有研究,于是天天出字符串题虐杀 zhx.何大爷今天为 字符串定义了新的权值计算方法 ...
- 特征缩放(Feature Scaling)
特征缩放的几种方法: (1)最大最小值归一化(min-max normalization):将数值范围缩放到 [0, 1] 区间里 (2)均值归一化(mean normalization):将数值范围 ...
- USACO 2009 Open 干草塔 Tower of Hay
USACO 2009 Open 干草塔 Tower of Hay Description 为了调整电灯亮度,贝西要用干草包堆出一座塔,然后爬到牛棚顶去把灯泡换掉.干草 包会从传送带上运来,共会出现N包 ...
- Promise链式调用 终止或取消
Promise回调分两种方法,then成功,catch失败 let promise = new Promise(function(resolve, reject){ resolve('第一次成功') ...
- ICEM——对msh文件或者cas文件重新划分边界
原视频下载地址:https://pan.baidu.com/s/1jIoKSuy 密码: m3uv
- 关于Java正则和转义中\\和\\\\的理解
定义 一个转义字符的目的是开始一个字符序列,使得转义字符开头的该字符序列具有不同于该字符序列单独出现时的语义. 转义就是指转换该字符的原本意义,从而变成另外的意义. \作为Java的转义字符 1.在j ...
- Kubernetes Pod 生命周期
一. Pod Hook Kubernetes 为我们提供了生命周期钩子,就是我们所说的Pod Hook,Pod Hook是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行.这是 ...
- 为什么集合类没有实现Cloneable和Serializable接口
为什么集合类没有实现Cloneable和Serializable接口? 答:克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的.因此应该由集合类的具体实现类 ...
- [代码质量] Maintainability Index (MI)
转载自: http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm ProjectCodeMeter Ma ...