问题描述
小蓝正在设计一个排水管道的工程,将水从西边一个离地面很高的水库引到东边的地面。
为了项目更有艺术性,小蓝建造了很多高低不同的的柱子用于支撑流水的水槽,柱子之间的间距相同。
当西边的柱子比东边相邻的柱子至少高 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,3,4...那么用程序来表达就是条件1:值和下标相加至少是原数组的长度n
- 但是这样还是不够的,因为如果要向原下降子序列里添加新的一项,要确保两项中间的所有数都能被合规修改,如数组4,2,3,1. 虽然4 和 3 都满足了至少是n的条件,但是3不能添加进递减数列,因为4和3中间还有一个数是没办法修改的,那么我们就得到下一个数 j 和前一个数 i 必须满足条件2: j <= i + i下标-j下标
- 条件已经凑齐了,但是可以直接开始做了吗?还是不行,直接开始就是上面的错误代码,还是避不开下标的搜索,那么还可以怎么简化呢?
- 可以对条件2进行移项:j+j下标 <= i+i下标,那么我们将值和下标绑定,直接对它们的和进行求不上升子序列不就行了吗?
- 所以条件都集齐了,可以开始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()