[题解] 楼房重建 (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;
}
posted @ 2020-07-27 23:45  RiverHamster  阅读(140)  评论(0编辑  收藏  举报
\