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] 的左右儿子,设为 ls
和 rs
:
- 若
maxnum[ls] < maxnum[lson[now]]
,则ls
没有贡献,直接递归Query(rs, maxnum[lson[now]])
即可。 - 否则
ls
有贡献,需要递归处理,但这样复杂度为 \(O(n)\) 级别。
发现此时对于rs
,其第一个贡献的值x
满足x>maxnum[ls]
,其贡献部分 与rs
对rson
贡献部分相同。
而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;
}