【题解】导弹拦截

题目描述

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

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

 

输入格式

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

 

输出格式

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

 

输入样例

389 207 155 300 299 170 158 65

 

输出样例

6

2

 

题解

  这题在洛谷上被强化了,所以$O(n^{2})$的暴力过不了。

  先说暴力做法,第一问就是求最长不上升子序列的版,dp就好。注意最大值不一定是$dp[n]$。

  第二问是一个贪心。假设我们记录第$1$到第$i$个导弹至少需要$cnt$个系统拦截,其中$f[j]$表示第$j$个系统最后拦截到的导弹高度。

  那么,当我们枚举到第$i$导弹时,我们可以枚举$f[j]$,如果所有$f[j]$都小于$a[i]$,那么我们就让$f[++cnt] = a[i]$,即多分配一个系统。

  如果有多个$f[j]$满足$f[j]>=a[i]$,我们就让其中最小的$f[j] = a[i]$即可。

#include <iostream>
#include <cstdio>

#define MAX_N (100000 + 5)

using namespace std;

int n;
int a[MAX_N];
int dp[MAX_N], ans;
int f[MAX_N], cnt;

int main()
{
    while(~scanf("%d", a + n + 1)) ++n;
    for(register int i = 1; i <= n; ++i)
    {
        for(register int j = 1; j < i; ++j)
        {
            if(a[i] <= a[j]) dp[i] = max(dp[i], dp[j]);
        }
        ++dp[i];
        ans = max(ans, dp[i]);
    }
    printf("%d\n", ans);
    int p;
    f[0] = 2147483647;
    for(register int i = 1; i <= n; ++i)
    {
        p = 0;
        for(register int j = 1; j <= cnt; ++j)
        {
            if(a[i] <= f[j] && f[j] < f[p]) p = j;
        }
        if(!p) p = ++cnt;
        f[p] = a[i];
    }
    printf("%d", cnt);
    return 0;
}
参考程序O(n^2)

     这个时候我们观察第一问的dp,其实这里看作求$a[n]$到$a[1]$的最长不下降子序列,这样就可以用树状数组找最大值来解决了。

  观察第二问,其实是Dilworth定理,也就是求最长上升子序列,再用树状数组即可。

  至于这个定理我也是看了dalao的解释后才懂得,这里我也写出来:

假设我们记录第$1$到第$n$个导弹至少需要$cnt$个系统拦截,其中$S_{i}$表示第$i$个系统拦截的导弹序列(不上升子序列)。

容易得到,在$S_{i + 1}$中定然能找到一个数大于$S_{i}$中的一个数(如果找不到,那么$S_{i}$应包括$S_{i + 1}$才对)。

此时我们把每个组对应的“一个数”连起来,会形成一个上升子序列,长度即为$cnt$。

我们设$a[1]$到$a[n]$的最长上升子序列$LIS$的长度为$len$,那么$len \geqslant  cnt$,又因为$S_{i}$中不可能出现$LIS$中的两个数(否则$S_{i}$就不满足不上升的性质了),所以$len = cnt$。

#include <iostream>
#include <cstring>
#include <cstdio>

#define MAX_N (100000 + 5)
#define MAX_A (50000 + 5) 

#define lowbit(x) ((x) & -(x))

using namespace std;

int n;
int a[MAX_N];
int maxa;
int ans;
int t[MAX_N];

int main()
{
    while(~scanf("%d", a + n + 1)) maxa = max(maxa, a[++n]);
    int tmp;
    for(register int i = n; i; --i)
    {
        tmp = 0;
        for(register int j = a[i]; j; j -= lowbit(j))
        {
            tmp = max(tmp, t[j]);
        }
        ++tmp;
        ans = max(ans, tmp);
        for(register int j = a[i]; j <= maxa; j += lowbit(j))
        {
            t[j] = max(t[j], tmp);
        }
    }
    printf("%d\n", ans);
    memset(t, 0, sizeof t);
    ans = 0;
    for(register int i = 1; i <= n; ++i)
    {
        tmp = 0;
        for(register int j = a[i] - 1; j; j -= lowbit(j))
        {
            tmp = max(tmp, t[j]);
        }
        ++tmp;
        ans = max(ans, tmp);
        for(register int j = a[i]; j <= maxa; j += lowbit(j))
        {
            t[j] = max(t[j], tmp);
        }
    }
    printf("%d", ans);
    return 0;
}
参考程序O(nlogn)

 

转载于:https://www.cnblogs.com/kcn999/p/10803126.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值