蓝桥<排水管道>题的题解(最长上升子序列的变式)

简介: 蓝桥杯排水管道AC题解,帮助你加深理解LIS最长上升子序列

@[TOC]

题目传送门

https://www.lanqiao.cn/problems/2355/learning/?contest_id=79

问题描述

小蓝正在设计一个排水管道的工程,将水从西边一个离地面很高的水库引到东边的地面。

为了项目更有艺术性,小蓝建造了很多高低不同的的柱子用于支撑流水的水槽,柱子之间的间距相同。

当西边的柱子比东边相邻的柱子至少高 1 时,水能正常通过水槽向东流,否则可能出现异常。

但是,小蓝发现施工方建造的柱子是忽高忽低的,不满足西边高东边低的要求。小蓝不得不修改柱子的高度以便水能正常通过水槽。同时,小蓝需要用 上所有的柱子,最东边的柱子高度至少为 1,最西边的柱子高度可以为任意高度。每修改一根柱子的高度,需要花费 1 的代价(不管高度改变多少代价都是 1)。

请问,小蓝最少花费多大的代价,可以让水能正常通过所有水槽?在修改时,小蓝只会把柱子的高度修改为整数高度。

输入格式

输入的第一行包含一个整数 n,表示柱子的数量。

第二行包含 n 个整数 h1 , h2 , ···, hn,从西向东表示每根柱子的高度。

输出格式

输出一行包含一个整数,表示答案。

样例输入

6
8 9 5 2 1 1

image.gif

样例输出

3

image.gif

样例说明

至少修改三根柱子,高度变为 8,7,5,3,2,1。

评测用例规模与约定

对于 30% 的评测用例,2 ≤ n ≤ 100,1 ≤ hi ≤ 100 。 对于 60% 的评测用例,2≤n≤1000,1≤hi≤10000。 对于所有评测用例,2 ≤ n ≤ 100000,1 ≤ hi ≤ 10^9。

运行限制

    • 最大运行时间:1s
    • 最大运行内存: 256M

    难度: 简单   标签: 省赛, 2020

    LIS 的一般做法

    首先这题给人的第一印象就是求下降子序列的最大长度,但是一般的动规只能达到O(n²)

    code

    n = int(input())
    a = list(map(int, input().split()))
    dp = [1 for i in range(n)]
    for i in range(n-1):
        for j in range(i+1, n): 
            if n-j <= a[j] <= a[i]+i-j: 
                dp[j] = max(dp[j], dp[i]+1)  # 经典动规,如果i能到j就考虑是否要将j接到i后面
    print(n-max(dp))

    image.gif

    LIS的优化做法

    很简短,但是不能AC,TLE了四个点,所以有大佬提出了O(nlogn)的优化,即dp[i]不再表示到 i 的最长子串长度了,而是表示以 i 结尾的子串,再单独由ans变量来存储长度,之所以从n到logn是因为多了一个单调数组,可以运用二分。

    code

    import bisect  # python的二分库 
    n = int(input()) 
    a = tuple(zip(map(int, input().split()), range(n))) 
    for i0, j0 in a: # 将第一个符合条件的筛出
        if i0 >= n: 
            dp = [(i0, j0)] 
            break 
    ans = 1 
    for i, j in a: 
        if n-j <= i and j > j0: 
            if i <= dp[-1][0]+dp[-1][1]-j: 
                dp.append((i, j)) 
                ans += 1 
        else: 
            if i > i0: 
                dp[0] = (i, j) 
            else: 
                m = bisect.bisect_left([k[0] for k in dp], i) 
                if i <= dp[m-1][0]+dp[m-1][1]-j: 
                    dp[m] = (i, j) 
                    print(n-ans)

    image.gif

    但是上面的代码是有误的,而且有很大的漏洞,因为我们需要快速搜索所取数的下标,因为有重复数据所以难以实现,就算实现了也会损失空间或者效率。接下来看看AC正解。

    正确思路

    理解了这些储备知识,就来到了这题的关键:什么叫西边比东边高,最东边至少得是1 。

    1. 1. 这里的条件首先可以归纳为从右边的1开始,每个至少得是2,3,4...那么用程序来表达就是条件1:值和下标相加至少是原数组的长度n
    2. 2. 但是这样还是不够的,因为如果要向原下降子序列里添加新的一项,要确保两项中间的所有数都能被合规修改,如数组4,2,3,1. 虽然4 和 3 都满足了至少是n的条件,但是3不能添加进递减数列,因为4和3中间还有一个数是没办法修改的,那么我3们就得到下一个数 j 和前一个数 i 必须满足条件2: j <= i + i下标-j下标
    3. 3. 条件已经凑齐了,但是可以直接开始做了吗?还是不行,直接开始就是上面的错误代码,还是避不开下标的搜索,那么还可以怎么简化呢?
    4. 4. 可以对条件2进行移项:j+j下标 <= i+i下标,那么我们将值和下标绑定,直接对它们的和进行求不上升子序列不就行了吗?
    5. 所以条件都集齐了,可以开始AC咯

    正确code

    def check(x, key):  # 手打二分,注意bisect不好使了,因为前面两个要手动添加才能传递是上升 
    还是下降
        l = 0
        r = len(x)-1
        while l < r:  # 选取这种2分是因为nlogn做法的性质,如果有疑问可以去百度一下最长上升子序列的优化
            m = l+r >> 1
            if x[m] >= key:
                l = m+1
            else:
                r = m
        return r
    n = int(input())
    a = zip(map(int, input().split()), range(n))  # 对值和下标进行打包
    a = [i+j for i, j in a if i+j >= n]  # 绑定并手动过滤
    b = []  # 存nlogn形式的dp
    ans = 0  # 存不上升子序列的长度
    for i in a:
        if not b or i <= b[-1]:
            b.append(i)
            ans += 1
        else:
            b[check(b, i)] = i
    print(n-ans)

    image.gif


    相关文章
    【LeetCode-每日一题】-718. 最长重复子数组
    【LeetCode-每日一题】-718. 最长重复子数组
    【动态规划刷题 15】最长定差子序列&& 最长的斐波那契子序列的长度
    【动态规划刷题 15】最长定差子序列&& 最长的斐波那契子序列的长度
    |
    1月前
    acwing 895 最长上升子序列1
    acwing 895 最长上升子序列1
    28 3
    |
    1月前
    acwing 896 最长上升子序列II
    acwing 896 最长上升子序列II
    25 2
    |
    6月前
    |
    JavaScript
    代码随想录 Day48 动态规划16 T647 回文子串 T516最长回文子序列
    代码随想录 Day48 动态规划16 T647 回文子串 T516最长回文子序列
    42 0
    |
    6月前
    |
    人工智能 算法 Java
    最长连续不重复子序列(蓝桥杯每日一题)
    最长连续不重复子序列(蓝桥杯每日一题)
    37 0
    |
    JavaScript 前端开发 C语言
    leetcode每日一题 2021/4/3 1143. 最长公共子序列
    leetcode每日一题 2021/4/3 1143. 最长公共子序列
    55 0
    |
    人工智能
    线性DP——AcWing 898. 数字三角形、AcWing 895. 最长上升子序列
    线性DP——AcWing 898. 数字三角形、AcWing 895. 最长上升子序列
    79 0
    |
    算法
    Leecode 300. 最长上升子序列
    Leecode 300. 最长上升子序列
    64 0
    【leetcode每日一题】1027. 最长等差数列
    【leetcode每日一题】1027. 最长等差数列