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 |
|
参考文献
小粉兔大佬的思路,未来还需要磨砺啊~