线性DP
- 线性DP-
定义:线性,线性,就是在一条线上进行的DP,那么就可以想到,某一处的值至于他的前面或者后面有关;
- 一:最长上升序列(LIS)
-
- 什么是LIS:
设有整数序列b1,b2,b3,…,bm,若存在下标 i1<i2<i3< …<in,且bi1<bi2<bi3< …<bin,则称 b1,b2,b3,…,bm中有长度为n的最长上升序列bi1 , bi2 ,bi3 ,…,bin;
- 什么是LIS:
-
- 分析:
我们设f[i]表示前i个数里,最长上升子序列的长度为多少,那么显而易见,f[i]仅与f[j],(j∈[1,i))有关,即f[i]的值只与前面的状态有关,
由此可得状态转移方程f[i]=max(f[i],f[j]+1 (j<i&&b[j]<b[i]));
- 分析:
-
- 代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N],n,f[N],qu[N],mm;
stack<int> q;
int LIS(int y){
for(int i=1;i<=y;i++)f[i]=1;
for(int i=2;i<=y;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]){
if(f[i]<f[j]+1){
qu[i]=j;
f[i]=f[j]+1;
}
}
}
}
int man=0xcfffffff;
for(int i=1;i<=y;i++){
if(man<f[i])man=f[i],mm=i;
}
return man;
}
int main(){
// freopen("stdi.txt","r",stdin);
int x;
while(scanf("%d",&x)!=EOF)a[++n]=x;
cout<<"max="<<LIS(n)<<endl;
for(int i=mm;i;i=qu[i])q.push(a[i]);
while(!q.empty())cout<<q.top()<<" ",q.pop();
// fclose(stdin);
}
-
- 记录路径:
•由状态转移方程可知,f[i]是由f[j]+1转移过来,所以如果f[j]+1>f[i],(j<i&&b[j]<b[i]),那么就用数组qu【】来记录f[i]是被谁更新的,最后找到最大值并从大到小输出即可。
- 记录路径:
- 二、例题
- 1. 拦截导弹
题面:
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式 输入只有一行,为若干个正整数,一次为导弹的高度。 输出格式 第一行为最多能拦截的导弹数; 第二行为要拦截所有导弹最少要配备的系统数
样例
样例输入
389 207 155 300 299 170 158 65
样例输出
6
2
•题目解析:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。说明第一问其实就是求最长不上升子序列。 那么第二问我们可以用贪心来做:每个导弹我们都必须拦截,那么我们将每个导弹都从目前的已有的导弹系统判断,如果可以拦截,那么就更新这个系统的最低射高否则就不得不新开一个系统来拦截。
证明: 用k[i]表示第i套系统依照我们的做法,每个导弹总是分配给第一个满足k[i]>=当前高度的系统,这样分会不会使后面的某个导弹原本可以加在该系统下,却因为本次更新导致不得不新开一个系统呢?
答案是不会 我们分配的条件是每个导弹总是分配给第一个满足k[i]>=当前高度的系统,那么这个系统之前的系统一定不满足k[i]>=当前高度,所以某个系统前面的系统的k[i]一定小于自己的k[i],并且更新后的k[i]>k[i-1],所以各个系统的k[i]成严格的递增,如图所示:
所以一个导弹可以被系统i拦截,那么一定可以被他以后的系统拦截,再加上我们将每个导弹分配给第一个满足条件的系统,那么就不会造成后面某个导弹可以被该系统拦截却不能被后面系统拦截的情况。
证毕
附上AC代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N],n,f[N],f1[N],cnt,b[N];
int LIS(int x[],int y,int z[]){
for(int i=1;i<=y;i++)z[i]=1;
for(int i=2;i<=y;i++){
for(int j=1;j<i;j++){
if(x[i]<=x[j])z[i]=max(z[i],z[j]+1);
}
}
int man=0xcfffffff;
for(int i=1;i<=y;i++){
if(man<z[i])man=z[i];
}
return man;
}
int main(){
// freopen("in.txt","r",stdin);
int x;
while(scanf("%d",&x)!=EOF)a[++n]=x;
cout<<LIS(a,n,f)<<endl;
for(int i=1;i<=n;i++){
bool f=0;
for(int j=1;j<=cnt;j++){
if(a[i]<=b[j]){
b[j]=a[i];
f=1;
break;
}
}
if(!f)b[++cnt]=a[i];
}
cout<<cnt;
// fclose(stdin);
}
- 2、友好城市
题目描述
Palmis国有一条横贯东西的大河,何有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不想交的情况下,被批准的申请尽量多。
输入格式
第1行,一个整数N(1 <= N <= 5000),表示城市数。 第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0 <= xi <= 10000)
输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。
样例
样例输入
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
样例输出
4
- 题目分析:
保证路不交叉,也就是要保证,在一岸按顺序递增的情况下,连到对岸的目的地不会小于上一条路的目的地,用f[i]表示连上第i条路可以连得最多的路的个数,可得状态转移方程:f[i]=max(f[i],f[j]+1);(a[i].to>=a[j].to);
附上代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=90005;
int n,f[N],cnt=1;
struct jj{
int fr,to;
bool operator <(const jj&x)const{
return fr<x.fr;
}
}a[N];
int main(){
cin>>n;
while(scanf("%d%d",&a[cnt].fr,&a[cnt].to)!=EOF){
cnt++;
}
cnt-=1;
sort(a+1,a+1+cnt);
for(int i=1;i<=cnt;i++){
f[i]=1;
for(int j=1;j<i;j++){
if(a[i].to>=a[j].to)f[i]=max(f[i],f[j]+1);
}
}
int man=0;
for(int i=1;i<=cnt;i++)man=max(man,f[i]);
cout<<man;
}