P4198 楼房重建

知识点: 线段树

原题面 Luogu


原题面有误。


题意简述

给定一个二维平面,给定 \(m\) 次操作。
平面中有 \(n\) 条线段,线段 \(i\) 两端点为 \((i,0)\)\((i,h_i)\)
初始时,对于每条线段,有 \(h_i=0\)
每次操作给定 \(x_i, y_i\),表示将线段 \(x_i\)\(h_{x_i}\) 变为 \(y_i\)
\((0,0)\) 处有一个人,人能看到线段 \(i\),当且仅当直线 \((0,0)\rightarrow (i,h_i)\) 不与 \((0,0) \rightarrow (j,h_j)(j<i)\) 中任意一条直线相交。
求每次操作后,能看到的线段的数量。
\(1\le x_i\le n\le 10^5\)\(1\le y_i\le 10^9, 1\le m\le 10^5\)


分析题意

考虑看到线段 \(i\) 的条件:
直线 \((0,0)\rightarrow (i,h_i)\) 不与 \((0,0) \rightarrow (j,h_j)(j<i)\) 中任意一条直线相交。
上述条件,等价于直线 \((0,0)\rightarrow (i,h_i)\) 的斜率大于 \((0,0) \rightarrow (j,h_j)(j<i)\) 中任意一条直线的斜率。

考虑抽象出每一条线段的斜率 \(d_i\),组成一个数列。
每次查询的答案,等价于以 \(d_1\) 开头的,严格上升序列的长度。
更形象地,可发现选择的子序列 等价于 \(d_1\sim d_n\) 组成的的 单调栈

考虑线段树维护,令每个节点维护对应区间的单调栈大小。
单点修改,每次只改变 \(\log n\) 个节点,不需要下传信息。
考虑如何快速合并两个子区间。

考虑当前节点为 now,需要将 lson[now]rson[now] 进行合并。
发现 lson[now] 的单调栈,一定会被包含在 now 的单调栈中,仅需考虑 rson[now] 贡献部分。
rson[now] 的贡献被 lson[now] 的单调栈所影响,考虑 rson[now] 第一个贡献的值 x,需要满足 x > maxnum[lson[now]]
不好直接做,考虑递归处理。

设函数 Query(x, y) 表示,节点 x 维护的区间中,选出的第一个元素 > y,的单调栈的长度。
rson[now] 的贡献即为 Query(rson[now], maxnum[lson[now]])
递归处理,考虑 rson[now] 的左右儿子,设为 lsrs

  1. maxnum[ls] < maxnum[lson[now]],则 ls 没有贡献,直接递归 Query(rs, maxnum[lson[now]]) 即可。
  2. 否则 ls 有贡献,需要递归处理,但这样复杂度为 \(O(n)\) 级别。
    发现此时对于 rs,其第一个贡献的值 x 满足 x>maxnum[ls],其贡献部分 与 rsrson 贡献部分相同。
    ans[rson] 已知,显然此时 rs 的贡献为 ans[rson] - ans[ls]

详见代码。


代码实现

//知识点:线段树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
#define ls (now_<<1)
#define rs (now_<<1|1)
const int kMaxn = 1e5 + 10;
//============================================================
int ans[kMaxn << 2];
double maxnum[kMaxn << 2];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
int Query(int now_, int L_, int R_, double pre_) {
  if (L_ == R_) return maxnum[now_] > pre_;
  int mid = (L_ + R_) >> 1;
  if (maxnum[ls] <= pre_) return Query(rs, mid + 1, R_, pre_);
  return Query(ls, L_, mid, pre_) + ans[now_] - ans[ls];
}
void Modify(int now_, int L_, int R_, int pos_, double val_) {
  if (L_ == R_) {
    maxnum[now_] = val_;
    ans[now_] = 1;
    return ;
  }
  int mid = (L_ + R_) >> 1;
  if (pos_ <= mid) Modify(ls, L_, mid, pos_, val_);
  else Modify(rs, mid + 1, R_, pos_, val_);
  maxnum[now_] = std :: max(maxnum[ls], maxnum[rs]);
  ans[now_] = ans[ls] + Query(rs, mid + 1, R_, maxnum[ls]);
}
//=============================================================
int main() { 
  int n = read(), m = read();
  while (m --) {
    int x = read(), y = read();
    double val = 1.0 * y / x;
    Modify(1, 1, n, x, val);
    printf("%d\n", ans[1]);
  }
  return 0; 
}
posted @ 2020-08-24 22:32  Luckyblock  阅读(128)  评论(0编辑  收藏  举报