基于贪心算法与二分查找(时间复杂度为o(nlogn))对导弹拦截问题(NOIP1999提高组)的深入研究

题目(输入输出样例请点链接)

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

1行,若干个整数(个数≤100000)

输出格式

2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

算法分析

我们可以很容易想到用最朴素的动态规划(o(nlogn))但再一看数据规模,要超时,不行。
再仔细想想,用LIS(longest increasing subsequence)的模板吧!

分析一下第一小问,标准的最长不上升子序列(这点理解应该没有太大问题吧!)

而针对于第二小问,你想想哪种情况你要多配备一个导弹拦截系统?不就是当后面有一个导弹的高度高于了前面那个系统你打的导弹的最低高度吗?所以你就可以去找有多少个上升的数呗!等等,这不就是最长上升子序列(LIS)吗?

而无论是最长不上升子序列还是最长上升子序列都一个求法:贪心+二分查找。

这儿讲一下具体做法(以最长上升子序列为例):
从数列头部开始扫描,针对每一个数分两种情况:

  1. 如果当前数大于现有上升子序列最后一个值,那么直接把它添加到上升子序列的末尾;
  2. 如果它小于最后一个数值,那么就从现有上升子序列的头部开始判断,代替掉第一个比它大的值,这样维护的上升子序列会在保证最长的前提下,保持每个数都是最小,以方便后来的数更大可能进序列(增加这个数列的潜力)!(但请注意:你最后得到的数列并不一定就是实际上的数列,只是它们两长度一定相同,这点请大家好好想想)

好吧,看看代码吧!

Code

#include<bits/stdc++.h>
using namespace std;

int cnt,h[100000+10]; 
int dp1[100000+10],dp2[100000+10],cnt1,cnt2;
int ans1,ans2;

void initialize(int array[],int &cnt)
{
	array[1]=h[1];
	cnt=1;
}

int main()
{
	while(scanf("%d",&h[++cnt])!=EOF);cnt--;
	initialize(dp1,cnt1);//初始化,把每个序列第一个设为第一枚导弹的高度
	for(int i=2;i<=cnt;i++)//求解最长不上升子序列
	{
		if(h[i]<=dp1[cnt1]) dp1[++cnt1]=h[i];//直接放在末尾
		else//二分查找这个数应该代替谁使整个数列更有潜力
		{
			int l=1,r=cnt1;
			while(l<r)
			{
				int mid=(l+r)/2;
				if(dp1[mid]>=h[i]) l=mid+1;
				else r=mid;
			}
			dp1[l]=h[i];
		}
	}
	ans1=cnt1;
	initialize(dp2,cnt2);
	for(int i=2;i<=cnt;i++)//求解最长上升子序列
	{
		if(h[i]>dp2[cnt2]) dp2[++cnt2]=h[i];//直接放在末尾
		else//二分查找这个数应该代替谁使整个数列更有潜力
		{
			int l=1,r=cnt2;
			while(l<r)
			{
				int mid=(l+r)/2;
				if(dp2[mid]<h[i]) l=mid+1;
				else r=mid;
			}
			dp2[l]=h[i];
		}
	}
	ans2=cnt2;
	printf("%d\n%d\n",ans1,ans2);//两个序列的长度就是答案
	return 0;
}

这就实现了网上传说中的o(nlogn)的做法。

总结与思考

  • 这就是找最长上升子序列的模板,记下来(而且以后应该要很熟练地打出来哦)
  • 这道题的关键就是反应出第二小问问的就是最长上升子序列。
  • 还是要多做点动态规划的题。

学习厌倦了?点我有更多精彩哦!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值