NOJ-1046-防卫导弹

153 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

防卫导弹

描述

一种新型的防卫导弹可截击多个攻击导弹。它可以向前飞行,也可以用很快的速度向下飞行,可以毫无损伤地截击进攻导弹,但不可以向后或向上飞行。但有一个缺点,尽管它发射时可以达到任意高度,但它只能截击比它上次截击导弹时所处高度低或者高度相同的导弹。现对这种新型防卫导弹进行测试,在每一次测试中,发射一系列的测试导弹(这些导弹发射的间隔时间固定,飞行速度相同),该防卫导弹所能获得的信息包括各进攻导弹的高度,以及它们发射次序。现要求编一程序,求在每次测试中,该防卫导弹最多能截击的进攻导弹数量,一个导弹能被截击应满足下列两个条件之一: a)它是该次测试中第一个被防卫导弹截击的导弹; b)它是在上一次被截击导弹的发射后发射,且高度不大于上一次被截击导弹的高度的导弹。

输入

多个测例。 每个测例第一行是一个整数n(n不超过100),第二行n个整数表示导弹的高度(数字的顺序即发射的顺序)。 n=0表示输入结束。

输出

每个测例在单独的一行内输出截击导弹的最大数目。

输入样例:

5 5 6 100 6 61 0

输出样例:

2

思路:

枚举每一个导弹i(i from 1 to n),这个导弹一定是由之前更高的导弹击中(设未击中导弹的时候,拦截导弹的高度为0x3f3f3f3f)枚举之前的所有导弹,一旦h[j]>=h[i],那么dp[i]=max{dp[i],dp[j]+1},当然被拦截的导弹i也可以作为第一个被拦截的导弹,dp[i]=1。最后在线性查找一遍最大的数量,ans=max{ans,dp[i]};

这个方法特别简单,不粘贴代码了。时间复杂度 O(n^2)。

这个复杂度不够优秀,下面就介绍O(nlogn)的做法。

先介绍一下lower_bound,upper_bound函数的使用方法

lower_bound(dp+1,dp+1+n,h[i])dp[]中第一个>=h[i]的下标 upper_bound(dp+1,dp+1+n,h[i])dp[]中第一个>h[i]的下标 lower_bound(dp+1,dp+1+n,h[i],greater< int >())dp[]中第一个<=h[i]的下标 greater< int >() == cmp(int x,int y){return x>y} upper_bound(dp+1,dp+1+n,h[i],greater< int >())dp[]中第一个<h[i]的下标

greater< int >()表示内置类型从大到小排序,比如说原序列是1,2,4,7,15,34,在greater< int >()的表示下, 1>2>4>7>15>34,lower_bound(num,num+6,7,greater< int >()) 返回greater< int >序列下第一个大于或等于被查数7的值, 即4;也就是返回的是原序列的中第一个小于或等于被查数7的值

下面介绍二分+动态规划来解决最长不上升子序列问题:

我们在O(n^2)的做法是将导弹i(1-n)之前的导弹j(1-i)都枚举一遍,然后更新dp[i]的值,但实际上根本没有必要将导弹j全部枚举一遍。我们定义一个dp[]数组,dp[]数组储存的是答案所求的最长的不上升子序列。那么我们对于导弹i,如果它要加入到dp[]中,那么就要找到dp[]数组中第一个小于h[i]的导弹,然后把它替换成h[i].(dp[]初始化为0),当已加入的导弹均高于当前导弹,就直接加入该导弹(无需替换)。

思考这样做的正确性:因为将dp[j]替换成了h[i],此时dp[]依旧是非递增序列,并且保证dp[]相较于之前是更加优化的(因为它整体要更大了)。

如何查找这个最先小于h[i]的dp[j]呢?显然dp[]是非递增序列,所以可以考虑用二分查找。对于很多单调性问题,首先考虑单调栈、单调队列、二分。尤其是这种带查找的最优化问题很有可能用二分。二分查找到最先小于的h[i]的进行替换。并且如果h[i]是直接假如dp[],记得更新dp[]数组的大小。最后的dp[]数组的大小就是答案。

PS:那如果问题是问,最少需要几枚导弹呢?这就是最长上升序列问题了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000+50;
int n,h[maxn],dp[maxn];
int max(int x,int y){
	if(x>=y)return x;
	return y;
}
int cmp(int x,int y){
	return x>y;
}
int main(){
	int i;
	while(1){
		cin>>n;
		if(n==0)break;
		for(i=1;i<=n;i++)cin>>h[i],dp[i]=0;
		int ans=0;
		for(i=1;i<=n;i++){
			int t=upper_bound(dp+1,dp+1+n,h[i],cmp)-dp;
			dp[t]=h[i];
			ans=max(ans,t);
		}
		cout<<ans<<endl;		
	}
	return 0;
}