You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1017. 怪盗基德的滑翔翼

零、前导知识

AcWing 895. 最长上升子序列

AcWing 896. 最长上升子序列 II

一、题目描述

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。

而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。

不得已,怪盗基德只能操作受损的滑翔翼逃脱。

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)

输入格式

输入数据第一行是一个整数K,代表有K组测试数据。

每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。

输出格式

对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。 数据范围

1≤K≤100,1≤N≤100,0<h<10000

输入样例

3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

输出样例

6
6
9

二、题目分析

题目要求:最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?这些建筑的高度必须是单调递减的。

从中间某个位置向两边递减高度,也就是中间最高,两边低,可以转化为从两边向中间走,单调递增,即LIS最长上升子序列问题。

并且题目中明确:假设城市中一共有N幢建筑排成一条线每幢建筑的高度各不相同。如此,就是一个严格上升子序列问题,可以在判断是不用写等号了。

三、题目总结

  1. 从左到右,求一遍最长上升子序列LIS问题。

  2. 从右到左,求一遍最长上升子序列LIS问题。

  3. 两次结果取最大值即可。

四、朴素O(N^2)版本

#include <bits/stdc++.h>

using namespace std;

const int N = 110;
int n;    // 楼房的个数
int w[N]; // 楼房的高度数组
int f[N]; // LIS结果数组DP结果

int main() {
    int T;
    cin >> T;
    while (T--) {
        // 保持好习惯,多组测试数据,记得每次清空结果数组
        memset(f, 0, sizeof f);
        int res = 0;

        cin >> n;
        for (int i = 1; i <= n; i++) cin >> w[i];

        // 从左到右,求一遍最长上升子序列[朴素O(N^2)版本]
        for (int i = 1; i <= n; i++) {
            f[i] = 1;
            for (int j = 1; j < i; j++)
                if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }

        // 反向求解 LIS问题
        for (int i = n; i >= 1; i--) {
            f[i] = 1;
            for (int j = n; j > i; j--)
                if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1);
            res = max(res, f[i]);
        }

        printf("%d\n", res);
    }
    return 0;
}

四、贪心+二分优化版本(O(NlogN))版本

#include <bits/stdc++.h>

using namespace std;

const int N = 110;
int n;    // 楼房的个数
int a[N]; // 楼房的高度数组

// 数组模拟栈
int f[N], fl;
int g[N], gl;
int res;

int main() {
    int T;
    cin >> T;
    while (T--) {
        // 保持好习惯,多组测试数据,记得每次清空结果数组
        memset(f, 0, sizeof f);
        memset(g, 0, sizeof g);
        fl = 0, gl = 0;

        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];

        // 正着求
        f[++fl] = a[1];
        for (int i = 2; i <= n; i++) {
            if (a[i] > f[fl])
                f[++fl] = a[i];
            else
                *lower_bound(f + 1, f + 1 + fl, a[i]) = a[i];
        }

        // 前半程的结果
        res = fl;

        // 反着求
        g[++gl] = a[n];
        for (int i = n - 1; i >= 1; i--) {
            if (a[i] > g[gl])
                g[++gl] = a[i];
            else
                *lower_bound(g + 1, g + 1 + gl, a[i]) = a[i];
        }
        // pk的最大结果
        res = max(res, gl);
        // 输出
        printf("%d\n", res);
    }
    return 0;
}

五、相关资源

最长上升子序列 (LIS) 详解+例题模板 (全)

### 六、经验总结

LIS序列不唯一,LIS长度唯一

举的栗子来讲,给出序列 ( 1, 7, 3, 5, 9, 4, 8),易得最长上升子序列长度为4,这是确定的,但序列可以为 (1, 3, 5, 8), 也可以为 (1, 3, 5, 9)

七、疑问

都排在一条直线了,遇到某个比自己高的,滑翔翼也飞不过去啊,这提供出来的LIS长度似乎无法成为侠盗基德的理论依据啊,他要是按这个数来跑,估计会卡在某个建筑上方,剩下的就哭吧,这道题的描述有问题,应该修改为 可以在遇到比自己高的建筑时,绕到更矮的建筑上 才合理。