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

问题描述

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

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

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

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

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

输入格式

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

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

输出格式

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

样例输入

6
8 9 5 2 1 1

样例输出

3

样例说明

至少修改三根柱子,高度变为 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

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

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)) 

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

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)

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

  1. 理解了这些储备知识,就来到了这题的关键:什么叫西边比东边高,最东边至少得是1 。
  2. 这里的条件首先可以归纳为从右边的1开始,每个至少得是2,3,4...那么用程序来表达就是条件1:值和下标相加至少是原数组的长度n
  3. 但是这样还是不够的,因为如果要向原下降子序列里添加新的一项,要确保两项中间的所有数都能被合规修改,如数组4,2,3,1. 虽然4 和 3 都满足了至少是n的条件,但是3不能添加进递减数列,因为4和3中间还有一个数是没办法修改的,那么我们就得到下一个数 j 和前一个数 i 必须满足条件2: j <= i + i下标-j下标
  4. 条件已经凑齐了,但是可以直接开始做了吗?还是不行,直接开始就是上面的错误代码,还是避不开下标的搜索,那么还可以怎么简化呢?
  5. 可以对条件2进行移项:j+j下标 <= i+i下标,那么我们将值和下标绑定,直接对它们的和进行求不上升子序列不就行了吗?
  6. 所以条件都集齐了,可以开始AC咯
def check(x, key):  # 手打二分
    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)

我一开始认为bisect是不能用的,因为怎么写答案都是错的,但是我后来发现bisect原来只能处理不下降序列(不严格递增数列)所以我们可以倒着取索引来使用bisect简化代码

from bisect import bisect_right


def main() -> None:
    n = int(input())
    a = [i+j for i, j in zip(list(map(int, input().split())), range(n)) if i+j >= n]
    b = []
    for i in range(len(a)-1, -1, -1):
        k = a[i]
        if not b or k >= b[-1]:
            b.append(k)
        else:
            b[bisect_right(b, k)] = k
    print(n-len(b))


if __name__ == "__main__":
    main()
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈子昂-北工大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值