今日复习了简单线性DP
Acwing.1017 怪盗基德的滑翔翼
题面
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
思路
对于每栋楼作为起点,左边右边都有一个能滑行的距离,包含初始建筑为1,即两边都生成一个LIS找出最大值即可
#include <iostream>
const int N=105;
using namespace std;
int a[N],h[N],ta[N];//f[i]表示以a[i]结尾的子序列长度的最大值
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t;cin>>t;
while(t--){
int n,res=0;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){//从前往后
h[i]=1;
for(int j=1;j<i;j++)
if(a[i]>a[j])
h[i]=max(h[i],h[j]+1);
res=max(res,h[i]);//取最大值
}
for(int i=n;i;i--){//从后往前
ta[i]=1;
for(int k=n;k>i;k--)
if(a[i]>a[k])
ta[i]=max(ta[i],ta[k]+1);
res=max(res,ta[i]);
}
cout<<res<<'\n';
}
return 0;
}
Acwing.1014 登山
题目
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2≤N≤1000
思路
这题和基德题一样都是以一个顶点开始,但是是求以这个顶点开始左右独立两边的子序列最大长度
因此只需要预处理两边长度再遍历一遍取最大即可,但要注意顶点被算了两次要-1
#include <iostream>
const int N=1e3+5;
using namespace std;
int a[N],f[N],t[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){//从前往后
f[i]=1;
for(int j=1;j<i;j++)
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
for(int i=n;i;i--){//从后往前
t[i]=1;
for(int j=n;j>i;j--)
if(a[i]>a[j])
t[i]=max(t[i],t[j]+1);
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,f[i]+t[i]-1);//以i为顶点的算了两次
cout<<res;
return 0;
}
P1091 [NOIP2004 提高组] 合唱队形
相同题目变形,只不过是最少出列人数,直接总人数减去最大左右子序列长度即可
cout<<n-res;
当需要求长度为k的上升子序列,只需要求之前是否存在长度为k-1最小数是否比a[i]小即可,其他数据可以去掉,因此可以采用,二分进行优化O(nlogn)
#include <iostream>
#include <algorithm>
#include <cstring>
const int N=105;
using namespace std;
int a[N],f[N],g[N],t[N];
// g[i] 上升子序列长度为 i 时结尾最小值
// len LIS 长度
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int len=0;
for(int i=1;i<=n;i++){//从前往后
int pos=lower_bound(g+1,g+len+1,a[i])-g;
f[i]=pos;
g[pos]=a[i];
len=max(len,pos);
}
len=0;
memset(g,0,sizeof g);
for(int i=n;i;i--){//从后往前
int pos=lower_bound(g,g+1+len,a[i])-g;
t[i]=pos;
g[pos]=a[i];
len=max(len,pos);
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,f[i]+t[i]-1);
cout<<n-res;
return 0;
}