NOIP1999提高组T1:导弹拦截

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围

雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。

输入样例

389 207 155 300 299 170 158 65

输出样例

6
2

算法思想(线性动规+贪心)

根据题目描述:导弹依次飞来,拦截时每一发炮弹都不能高于前一发的高度。那么最多能拦截的导弹数,其实就是最长的下降(非严格下降)子序列的长度。

第二问求最少要配备多少套这种导弹拦截系统?可以使用贪心思想,流程如下:
从前向后遍历每一个导弹高度,对于每一个数来说,有两种情况:

  1. 所有拦截系统的最低高度都小于当前导弹高度,需要增加一套系统
  2. 找到第一套系统,其拦截的最低高度大于等于当前导弹的高度,更新该系统的最低拦截高度。

代码实现

#include <iostream>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int a[N], f[N], b[N];
int main(){
    int n = 0, x;
    while(cin>>x) a[++n] = x;
    int res = 0;
    for(int i = 1; i <= n; i++){
        f[i] = 1;
        for(int j = 1; j < i; j++){
            if(a[j] >= a[i]) f[i] = max(f[i], f[j] + 1);
        }
        if(res < f[i]) res = max(res, f[i]);
    }
    cout<<res<<endl;
    int cnt = 0;
    for(int i = 1; i <= n; i++){
        int k = 0; 
        //b[k]表示第k套拦截系统能够拦截的最小高度,并且b[]序列一定是严格单调递增的
        while(k < cnt && b[k] < a[i]) k++;
        if(k >= cnt) cnt++;
        b[k] = a[i];        
    }
    cout<<cnt<<endl;
    return 0;
}

扩展1:狄尔沃斯定理(Dilworth’s theorem)

原链最大长度 等于 反链划分数最小值。

原链反链
(严格)上升子序列不上升子序列
不上升子序列(严格)上升子序列
(严格)下降子序列不下降子序列
不下降子序列(严格)下降子序列

因此,本题中第二问求最少要配备多少套这种导弹拦截系统(即不上升子序列的划分数),可以转变为求最长(严格)上升子序列的最大长度。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 50010;
int a[N], f[N], g[N];
int main()
{
    int n = 0, x;
    while(cin >> x) a[++ n] = x;
    int ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++)
        {
            if(a[j] >= a[i]) f[i] = max(f[i], f[j] + 1);
        }
        ans = max(ans, f[i]);
    }
    cout << ans << endl;
    //根据狄尔沃斯定理,求反链(最长上升子序列)的最大长度
    ans = 0;
    for(int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for(int j = 1; j < i; j ++)
        {
            if(a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
        }
        ans = max(ans, f[i]);
    }
    cout << ans << endl;
}

扩展2:贪心 + 二分

算法思想

对于序列 ( 389 , 207 , 155 , 300 , 299 , 170 , 158 , 65 ) (389,207,155,300,299,170,158, 65) (389,207,155,300,299,170,158,65)

使用 q [ ] q[] q[]存储不同长度的不上升序列中最后一个数最大值,例如:

  • 长度为1的所有不上升序列中,结尾最大的是 ( 389 ) (389) (389),因此 q [ 1 ] = 389 q[1] = 389 q[1]=389
  • 长度为2的所有不上升序列中,结尾最大的是 ( 389 , 300 ) (389, 300) (389,300),因此 q [ 2 ] = 300 q[2] = 300 q[2]=300
  • 长度为3的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 ) (389,300,299) (389,300,299),因此 q [ 3 ] = 299 q[3] = 299 q[3]=299
  • 长度为4的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 ) (389,300,299,170) (389,300,299,170),因此 q [ 4 ] = 170 q[4] =170 q[4]=170
  • 长度为5的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 , 158 ) (389,300,299,170,158) (389,300,299,170,158),因此 q [ 5 ] = 158 q[5] =158 q[5]=158
  • 长度为6的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 , 158 , 65 ) (389,300,299,170,158,65) (389,300,299,170,158,65),因此 q [ 6 ] = 65 q[6] =65 q[6]=65

通过观察,发现 q [ ] q[] q[]是严格单调递减的序列,可以使用二分进行查找。

算法实现

通过构造 q [ ] q[] q[],求出最长不上升子序列的长度 m m m

  • 遍历序列中的每个数 a [ i ] a[i] a[i]
    • 通过二分查找,在 q [ ] q[] q[]数组中找到最后一个大于等于 a [ i ] a[i] a[i]的位置 L L L
      • q [ L + 1 ] q[L + 1] q[L+1]更新为 a [ i ] a[i] a[i]
      • L + 1 > m L + 1 > m L+1>m,则更新 m m m
  • 输出 m m m

代码实现

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 1e5 + 10, INF = 1e9;
int a[N], f[N], q[N];
int main()
{
    int n = 0, x, len;
    while(cin >> x) a[++ n] = x;
    //贪心+二分求最长不上升子序列长度
    //q[len]表示长度为len的最长不上升子序列最后一个元素
    //即满足长度为len时的最长不上升子序列最后一个元素的最大值
    //序列q[]为一个不上升的序列
    len = 0; q[len] = INF;
    for(int i = 1; i <= n; i ++)
    {
        if(a[i] <= q[len]) q[++ len] = a[i];
        else
        {
            int L = 0, R = len, mid;
            while(L < R)
            {
                //二分查找q[]序列中最后一个大于等于a[i]的位置L
                mid = (L + R + 1) / 2;
                if(q[mid] >= a[i])  L = mid;
                else R = mid - 1;
            }
            q[L + 1] = a[i]; //注意,更新的L + 1位置的值
        }
    }
    cout << len << endl;
    //根据狄尔沃斯定理,要求反链(不上升子序列)的最小划分数
    //即求原链(最长上升子序列)的最大长度
    len = 0; q[len] = -INF;
    //q[len]表示长度为len的最长严格上升子序列最后一个元素
    //即满足长度为len时的最长严格上升子序列最后一个元素的最小值
    //序列q[]为一个严格单调上升的序列
    for(int i = 1; i <= n; i ++)
    {
        if(a[i] > q[len]) q[++ len] = a[i];
        else
        {
            int L = 0, R = len, mid;
            while(L < R)
            {
                mid = (L + R) / 2;
                if(q[mid] >= a[i]) R = mid;
                else L = mid + 1;
            }
            q[L] = min(q[L], a[i]);
        }
    }
    cout << len << endl;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值