蓝桥杯 算法训练 拦截导弹 By Assassin [最长下降子序列nlogn]

问题描述
  某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

  输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
  一行,为导弹依次飞来的高度
输出格式
  两行,分别是最多能拦截的导弹数与要拦截所有导弹最少要配备的系统数
样例输入
389 207 155 300 299 170 158 65
样例输出
6
2

思路:非常明显而且经典的最长下降子序列的题目,题目中又说明了每个数值大小不同更是降低了难度。
题目要求分为两部分:
1.求最长下降子序列
2.求需要几串下降的子序列才能包含全串

问题一 :求最长下降子序列

因为我们在题目中看到数值不超过30000那么通常的dp用O(N^2)的方法是会超时的!那么我们需要优化一下。思路就是贪心+二分查找

求一个序列的最长上升子序列,可以用贪心的思想来做。

例:389 207 155 300 299 170 158 65

要求它的最长递减子序列,具体步骤如下:

389

389 207

389 207 155

389 300 155 //207被300替换了,207是三个数中刚好小于300的数,207能接的后序列,300照样能接,而且有更大的空间可以接

389 300 299 //此次用299替换155,一样是因为有更大的空间可用,在这里就体现了用300来替换207的正确性

389 300 299 170

389 300 299 170 158

389 300 299 170 158 65

这样就得到了一个最长递减子序列,而且更有可能接更多的数
最后这个串的长度就是最大的长度!
我们假定变化的数组为X,源数据数组为Y,那么假定Y[i]比X当前最小值还小,那么加到X的后面,否则二分查找X串中比Y[i]小的最大的值,并替换成Y[i]。具体如上!

问题二 :求需要几串下降的子序列才能包含全串

事实上这个继续用贪心就可以了,但是需要结合队列用。
前提是我们要求队列数,假定一个下降子序列为一队,只要一队一队去掉,然后统计有几队即可!而且,不需要每一队是最优的!因为只要出现一个值比前一个值大(顺序对),那么就必须再有一个队,比如说
8 7 6 3 5 4 2 1
那么一定是2队,可以使8 7 6 3 2 1+5 4(5比前面的3大)
并不需要是每队最优的。(说不太清,自己体会一下吧)
那么实现的时候我们可以用队列,每一轮摘取出一队,递减的数直接pop掉,出现比前面大的情况就压到队列的尾部。知道所有的队都被提取了出来!
哎,说不太清,直接看代码吧…5_5

#include<bits/stdc++.h>
using namespace std;
int value[30001],length=0;
int getmax[30001],maxlength,maxpos;     //maxlength表示下降子序列的最大长度 ,讲解中的X串 

void dfs(int start,int end,int temp){   //二分查找 
    if(start>end) return; 
    int mid=(start+end)/2;
    if(getmax[mid]<temp){
        maxpos=mid;                     //比Y[i]小的最大的值的位置 
        dfs(start,mid-1,temp);          //继续向前搜索 
    }
    else{
        dfs(mid+1,end,temp);
    }
}

int main(){
    //freopen("input.txt","r",stdin);
    while(scanf("%d",&value[++length])!=EOF);   //输入,真实的长度为length-1,自己试一下 
    getmax[1]=value[1];
    maxlength=1;
    for(int i=2;i<length;i++){                  
        if(value[i]<getmax[maxlength]){         //如果Y[i]比X当前最小值还小 
            getmax[++maxlength]=value[i];       //那么Y[i]加到X的后面
        }   
        else{
            dfs(1,maxlength,value[i]);          //否则二分查找X串中比Y[i]小的最大的值,并替换成Y[i] 
            getmax[maxpos]=value[i];
        }
    }
    cout<<maxlength<<endl;
    length--;                                   //更正length Y的长度 
    queue<int>q;                                //用队列实现lines数的统计 
    int lines=0,maxtemp,len;                    // maxtemp代表每队当前的最小值 
    for(int i=1;i<=length;i++){
        q.push(value[i]);
    }
    while(!q.empty()){
        maxtemp=q.front();
        len=length;                             //每次初始化当前queue的长度 
        for(int t=1;t<=len;t++){
            if(q.front()<=maxtemp){
                maxtemp=q.front();
                q.pop();
                length--;                       //更新length长度 
            }
            else{
                q.push(q.front());              //如果出现顺序对那么压回队列 
                q.pop();
            }
        }
        lines++;                                //统计队数 
    }
    cout<<lines<<endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值