洛谷P4198 楼房重建 题解

Part1.自己一开始是怎么想的

我一开始的想法是先考虑什么情况下是看不见的。

如果是 \(i < j\) 的话可以直接看 \(j\) 的斜率和 \(i\) 的斜率就是比较 \(\frac{h_i}{i}\) 的大小关系来判断。所以说我们是要单点修改一个点的斜率。我们要统计的东西很复杂,并没有想明白,然后不知道用什么来维护。

Part2.正解是怎样的

阅读题解可以得知一种线段树的写法。我们定义 \(len\) 表示一段区间内的可见建筑的个数。

抽象了一下问题:整个 \(1-n\) 的区间内,每一个大于上一个选了的必须选,小于等于上一个选了的必须不选,求最终的序列长度。

经过观察发现区间是固定的,大区间的答案是可合并的,修改是简单的,考虑线段树维护。

线段树中有两个值。一个存储区间最大\(len\),一个存储的是区间内的斜率最大值\(maxx\)。

考虑如何修改?直接修改,\(O(logn)\)。重点在于如何合并区间(pushup)。

现在考虑两个小区间,这两个小区间的\(len\) 和 \(maxx\) 是我们可以直接用的。可以发现,左区间的答案是可以直接继承的。现在我们只需要考虑右区间的答案。

我们是这么更新的 tree[u].len=tree[ls].len+pushup2(tree[ls].maxx,rs,mid+1,rt),我们发现,确实是直接继承的左儿子的答案。右儿子的答案就是递归算的。为什么要递归算呢?因为左边的数值实际上是会对右边的答案造成影响的,所以不能直接继承答案,要一点一点算出来左边的影响。传一个数值 \(lx\) 代表需要满足大于 \(lx\) 才能被看见。这个时候我们看 pushup 的核心分类讨论:

  1. maxx<=lx ,结束
  2. a[l]>lx,这时候可以剪枝,直接返 \(len_x\)
  3. l==rreturn a[l]>lx
  4. 定义 \(s_1\) 为左区间,\(s_2\) 为右区间。
  • s1.maxx <= lx , return pushup(lx,s2,mid+1,r)
  • s1.maxx > lx , return tree[u].len-tree[ls].len+pushup(lx,s1,l,mid)

要讲一下为什么是 tree[u].len-tree[ls].len 而不是 tree[rs].len,因为这里的更新是第一次,但是 tree[u] 已经完成了更新,右区间会受到左区间的影响,我们剪掉这种形式就是对的。

修改就是 \(O(logn^2)\)的。

Part3.差在哪里,如何解决?

没有理清楚自己要干什么。没有想到这种东西是可以合并的,然后就用线段树维护。

解决方法:我积累到了这种思考方式和这种题目的解决办法,感觉就能够解决类似的问题。

Part4.编码的困难,调出来的错误

  1. 没有
  2. 有小的地方打错。

Part5.收获有什么

积累了这种类似的问题的解决方案,拿到这种修改查询的东西首先要理清楚自己要干什么,看他的等价问题是什么,然后思考是否可以合并,然后大胆假设,小心分讨!

Part6.时间主要花在哪里了

理解正解最后的减去和递归的pushup,这种还是见少了,要见多识广才能缩短时间。