[题解] 楼房重建 (LG3800)
题意
一个平面上有 \(n\) 个点,\(x\) 为 \([1, n]\) 之间的整数。
每次修改一个点的 \(y\),求平面的上凸壳大小,即斜率的单调栈大小。
做法
首先想到直接单调栈维护,因为有修改,可能用到线段树等支持区间合并的数据结构。
但单调栈不能快速合并,显然不能做。
考虑每次合并左右两个区间,左边的最大值会对右边的长度造成影响,所以在每个点维护区间 max,单调栈的长度 \(len\)。
每次合并时,左边的区间不用计算,可以直接加上。右边可以递归处理:
设左边的最大斜率 \(k\)。
- \(\max l \le k\),则跳过左边,在右边递归处理。
- \(\max l > k\),则在左边递归处理,显然 \(k\) 对右边没有影响,直接加上即可。
怎么维护右边受到左边限制的凸壳大小?因为 \(x\) 点的凸壳是左边和右边(受左边限制)的并,求 \(len_x - len_l\) 即可。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 200005;
const double eps = 1e-10;
double mx[N * 4];
int len[N * 4];
int getlen(int x, int l, int r, double k){
if(l == r) return mx[x] - k > eps;
int mid = (l + r) >> 1;
if(mx[x << 1] - k > eps) return getlen(x << 1, l, mid, k) + len[x] - len[x << 1];
else return getlen(x << 1 | 1, mid + 1, r, k);
}
void modify(int x, int l, int r, int p, double v){
if(l == r){
mx[x] = v; len[x] = 1;
return;
}
int mid = (l + r) >> 1;
p <= mid ? modify(x << 1, l, mid, p, v) : modify(x << 1 | 1, mid + 1, r, p, v);
mx[x] = max(mx[x << 1], mx[x << 1 | 1]);
len[x] = len[x << 1] + getlen(x << 1 | 1, mid + 1, r, mx[x << 1]);
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i=1; i<=m; i++){
int p, h;
scanf("%d%d", &p, &h);
modify(1, 1, n, p, 1. * h / p);
printf("%d\n", len[1]);
}
return 0;
}