P4198 楼房重建
考点
- 线段树
题解
见思路
思路
普通想法
单点修改,区间询问,考虑线段树。
设斜率等于h(y) / y
,如果点(y, h(y))
要被看到,那么斜率肯定比之前的最大斜率还大。
设s
代表当前区间最大值,c
代表当前区间,从区间左端点仰望可以看见的建筑数量。
合并时,左孩子的c
可以直接继承到父结点,因为左孩子的左端点是l
,父结点的左端点也是l
,
右孩子的c
则不可以直接继承到父结点,因为右孩子是以mid + 1
为左端点,而父结点的左端点是l
,
即这行代码:
1 | c[p] = c[lc] + f(rc, mid + 1, r, id[lc]); |
也就是说,左孩子的加入会对右孩子的答案有如下影响:
假设当前父结点的区间为[l, r]
,左孩子为lc
,区间为[l, mid]
;
右孩子为rc
,区间为[mid + 1, r]
如果
lc
的斜率最大值,大于rc
的左孩子斜率最大值。显然是无法从父结点的左端点
l
看见rc
左孩子的任一房子,因为全被挡住了;但右孩子情况未知。这种情况下,
rc
的左孩子无法对答案有贡献,递归进rc
的右孩子,也就是这行代码:1
return 0 + f(rc, mid + 1, r, x);
如果
lc
的斜率最大值,小于rc
的左孩子斜率最大值。此时
lc
就无法对rc
的右孩子有影响,且rc
的右孩子根据的左端点就是mid + 1
,那么就可以把
rc
的右孩子的贡献并入答案中,然后再对rc左孩子
进行递归。1
return f(lc, l, mid, x) + (c[p] - c[lc]);
但是要注意!!!!贡献绝对不是
c[rc右孩子]
,绝对不是!!!!!!!!!!!!!!!!!!!!!再次观察代码,我们自始至终都没有直接保存过右孩子的贡献,都是左孩子贡献+递归右孩子的结果,没有可加性!
1
c[p] = c[lc] + f(rc, mid + 1, r, id[lc]);
最重要的是,
c[rc右孩子]
是rc右孩子
以(mid + 1 + r) >> 1
为左端点得到的结果,我们现在需知道rc右孩子
以mid + 1
为左端点时的贡献,完全就是两码事。所以,需要使用
c[rc] - c[rc左孩子]
得到c[rc右孩子]
最终得到代码:
1 |
|
升级想法
很多时候并没有可减性,比如取极值,按位与或等等。
所以可以改一下定义,c
代表当前区间,rc
的可见房屋数量;f
是每个区间的总可见房屋数量。
1 | int f(int p, int l, int r, int x) { |
这样一来,就不存在减法了。
总代码如下:
1 |
|
参考文献
小粉兔大佬的思路,未来还需要磨砺啊~