洛谷P1020导弹拦截——最长不升/上升子序列

一.说明

OK,今天我们来看一看这一道诡异的洛谷P1020导弹拦截,这道题我也是想了好久,翻了好多教程,当时讲的都很复杂,还有什么dilworth定理,对于我一个初中生蒟蒻来说实在是太恐怖了

二.题目

题目传送门

题目描述

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

输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

一行,若干个整数,中间由空格隔开。

输出格式

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

输入输出样例

输入

389 207 155 300 299 170 158 65

输出

6

2

解读

这玩意儿第一题就是求一个最长不升子序列,然后就是求可以把这个序列分成几个不升子序列

三.分析

什么是最长不升子序列

比如这里给一个序列,就拿题目的样例来看:

389 207 155 300 299 170 158 65

比如这个序列中,我们会选择标红的六个,这就是最长不升子序列

选取怎样的子序列

我们认为每一个最长不升子序列都有各自的潜力,什么意思呢?比如:

200 100 99

200 100 59

这两个都是最长不降子序列,那么很明显,第一个的潜力更大,为什么呢?

我们想象一下这两个序列都在一个更大的序列里,比如后面一个数字是70,那么很明显第一个可以将70加在后面,而第二个不可以将70加在后面,因为59大于70.

代码实现(第一题)

题目要求,这里要使用O(nlogn)的解法,要不然会超时

我们首先定义一个长度为输入序列长度的数组tail,tail的第i个元素表示长度为i的最长不升子序列的尾元素,比如tail[3]就是长度为3的最长不升子序列的结尾元素。

然后我们要想一想,这个tail数组有什么特点,tail[1]是长度为1的最长不升子序列的结尾元素,依次类推,这个tail是一个递减的数组,因为每第i个元素是在第i-1个元素的基础上,在尾部追加了一个小于i-1的数字,以此类推,所以这时一个降序排列的数组。

int binary_search(int from, int to, int value) {
    int left = from, right = to;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (tail[mid] >= value) {
            left = mid + 1;
        }
        else {
            right = mid-1;
        }
    }
    return left;
}
tail[1] = arr[1];
for (int i = 2; i <= len; i++) {
    if (arr[i] <= tail[ans]) {
        tail[++ans] = arr[i];
    }
    else {
        int j = binary_search(1, ans, arr[i]);
        tail[j] = arr[i];
    }
}

我们现在来解释一下代码在干嘛

首先是tail[1]=arr[1]中,我们可以先初始化在还没有开始动态规划的时候,我们的长度为1的最长不升子序列的结尾元素就是输入数组arr的第一个元素。

随后开始从2号元素开始循环直到len,我们每一次都要去选择这个arr[i]应该去哪里,用ans来存储当前的最长的不升子序列,初始化为1

两种情况:

如果当前值大于我们tail[ans]中的值,也就是说这个当前的值是可以追加到这个序列后面的,我们就把ans++,然后tail[ans]中存放当前这个值

如果当前值小于我们tail[ans]中的值,我们就要在1~ans的范围内寻找第一个小于当前值的值并将这个值替换掉,为什么要这样子做?我们就要运用到之前讲的潜力,我们将这个小一点的值替换成这个大的,我们最长不升子序列的潜力就增加了。我们之前讲了tail是降序排列的,我们用一个binary_search来查找第一个小于当前值的下标,复杂度O(logn),加上外层循环,整体程序复杂度为O(nlogn)

代码实现(第二题)

这里我们就要用这个神奇的dilworth定理,通俗的讲,这个定理就是在说:

一个序列中可以分割最长不升子序列的数量=这个序列中最长上升子序列的长度

所以第二题就是在问:这个序列中最长上升子序列的长度

逻辑和第一题相似:

tail[1] = arr[1];
for (int i = 1; i <= len; i++) {
    if (arr[i] > tail[ans1]) {
        tail[++ans1] = arr[i];
    }
    else {
        int j = lower_bound(tail + 1, tail + 1 + ans1, arr[i]) - tail;
        tail[j] = arr[i];
    }
}

代码和第一问相似,注意

最长上升子序列是不可以有相等的元素的,比如:2 3 3就不是上升子序列,lower_bound是不返回小于 key 的元素

#include<bits/stdc++.h>
using namespace std;
int x, arr[100005], tail[100005], len = 0, ans = 1, ans1 = 1;
int binary_search(int from, int to, int value) {
    int left = from, right = to;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (tail[mid] >= value) {
            left = mid + 1;
        }
        else {
            right = mid-1;
        }
    }
    return left;
}
int main() {
    while (cin >> x) { arr[++len] = x; }
    tail[1] = arr[1];
    for (int i = 2; i <= len; i++) {
        if (arr[i] <= tail[ans]) {
            tail[++ans] = arr[i];
        }
        else {
            int j = binary_search(1, ans, arr[i]);
            tail[j] = arr[i];
        }
    }
    memset(&tail, 0, sizeof(tail));
    printf("%d\n", ans);
    tail[1] = arr[1];
    for (int i = 1; i <= len; i++) {
        if (arr[i] > tail[ans1]) {
            tail[++ans1] = arr[i];
        }
        else {
            int j = lower_bound(tail + 1, tail + 1 + ans1, arr[i]) - tail;
            tail[j] = arr[i];
        }
    }
    printf("%d", ans1);
    return 0;
}

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值