洛谷 P1020 导弹拦截 菜鸟题解

有志者,事竟成,破釜沉舟,百二秦关终属楚。
苦心人,天不负,卧薪尝胆,三千越甲可吞吴。

P1020 导弹拦截

题目链接:

https://www.luogu.com.cn/problem/P1020

在这里插入图片描述

题目解释:

首先就是要找到一个最长非上升子序列,这是第一问,第二问就是非上升序列有多少个。

问题分析:

就是优化最长非上升子序列的问题,要求时间复杂度 n l o g n nlog_n nlogn n 2 n^2 n2的时间复杂度会爆,所以得优化。

题目思路:

首先新建一个数组dp[],和变量int,分别用来记录非上升子序列,和当前最长非上升子序列的长度。

看样例:

389 207 155 300 299 170 158 65
首先存在最长非上升子序列:389 300 299 170 158 65, 长度为6
其中207 155,这个序列需要第二套设备
所以答案就是6 2

首先开始手动模拟

记住这是动态规划题目,dp数组只需要存储一种状态而不是准确的答案。

dp数组中存储一个非上升子序列,当出现一个小于 d p [ l e n ] dp[len] dp[len]的数字的时候,就执行 d p [ + + l e n ] = a [ i ] dp[++len] = a[i] dp[++len]=a[i],这时表明,当前最长长度发生变化,更新当前最长长度的最小值。

如果出现一个大于 d p [ l e n ] dp[len] dp[len]的数字 a [ i ] a[i] a[i]的时候,就使用二分查找,在dp数组中找到一个大于 a [ i ] a[i] a[i]的位置,然后把那个位置的数字替换为 a [ i ] a[i] a[i]

这意味着,如果后面出了刚好比 a [ i ] a[i] a[i]小的序列,我就可以把原来的dp数组给替换了,而替换的过程len的大小并不变。如果到时候len值变化了,说明一定有更长的新的序列出现了。如果我一直在替换dp数组中的,但是len值没变化,其实这个时候就相当于,我取得最长非上升子序列还是len值刚刚更新时的那个dp数组。

第一个问题解决。

第二个问题:

怎么解决多个非上升子序列:

和上一问思路差不多。新建一个数组dp2[],然后只要有小的数字出现,就替换,如果有大的数字出现,就加在后面,有小的数字出现即 d p 2 [ l e n 2 ] > = a [ i ] dp2[len2] >= a[i] dp2[len2]>=a[i],说明当前的设备可以满足,设备数量并不需要满足,如果有大的数字出现即 d p 2 [ l e n 2 ] < a [ i ] dp2[len2] < a[i] dp2[len2]<a[i],说明当前的高度已经无法满足,需要另外一套新的设备,套用上一问的思路,直接二分就可以。

AC代码:

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

const int N = 1e5 + 7;
int f[N], d1[N], d2[N];

int main(){
    ios::sync_with_stdio(false);
    int n = 0, len1 = 1, len2 = 1;
    while (cin >> f[++n]);  n--;
    d1[1] = d2[1] = f[1];
    for (int i = 2; i <= n; i++){
        //只要高度比当前设备最后一次的高度低,那就可以继续使用
        if (d1[len1] >= f[i])   d1[++len1] = f[i];
        else{
            int p = upper_bound(d1 + 1, d1 + 1 + len1, f[i], greater<int> ()) - d1;
            d1[p] = f[i];
        }
        //只要高度不提高,就不需要另外一套设备
        if (d2[len2] < f[i])    d2[++len2] = f[i];
        else{
            int p = lower_bound(d2 + 1, d2 + 1 + len2, f[i]) - d2;
            d2[p] = f[i];
        }
    }
    cout << len1 << " " << len2 << endl;
    return 0;
}

需要注意两个函数:
lower_bound(),找到第一个大于等于的数的位置,返回值为指针,并不是下标所以要减去首地址。
upper_bound(),找到第一个大于的数的位置,返回值同样为指针。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值