今日总结2024/5/5

文章介绍了如何运用动态规划方法解决两个问题:怪盗基德利用滑翔翼在城市建筑间逃生时最大化经过不同建筑数,以及ACM登山队规划游览景点时避免连续相同海拔,最大化浏览数量。分别通过计算左右两边的最长递增子序列长度来找到答案。
摘要由CSDN通过智能技术生成

今日复习了简单线性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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值