P4198 线段树
题目描述
小 A 的楼房外有一大片施工工地,工地上有 N N N 栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。
为了简化问题,我们考虑这些事件发生在一个二维平面上。小 A 在平面上 ( 0 , 0 ) (0,0) (0,0) 点的位置,第 i i i 栋楼房可以用一条连接 ( i , 0 ) (i,0) (i,0) 和 ( i , H i ) (i,H_i) (i,Hi) 的线段表示,其中 H i H_i Hi 为第 i i i 栋楼房的高度。如果这栋楼房上任何一个高度大于 0 0 0 的点与 ( 0 , 0 ) (0,0) (0,0) 的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了 M M M 天。初始时,所有楼房都还没有开始建造,它们的高度均为 0 0 0。在第 i i i 天,建筑队将会将横坐标为 X i X_i Xi 的房屋的高度变为 Y i Y_i Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小 A 数数每天在建筑队完工之后,他能看到多少栋楼房?
题意
单点修改,维护最长严格上升子序列。
思路
考虑用线段树。维护每个节点的 mx,cnt
,分别表示对应区间的最大值和最长严格上升子序列长度。建树很简单(甚至不用建)。因为是单点修改也用不到pushdown,查询的话 ans(1)
即为答案,所以重点在如何修改以及维护。
我们考虑左右两个区间合并,显然合并后 mx(p) = max(mx(p << 1), mx(p << 1 | 1))
,而 cnt(p) = cnt(p << 1) + query(p << 1 | 1, mx(p<<1))
。翻译过来就是 区间的最大值等于左右两个子区间的最大值中大的那个,区间的最长子序列长度是 左区间的最长子序列长度 加上 右区间中比左区间最大值大的个数
。正确性比较容易证明,关键问题就是如何求 右区间中比左区间最大值大的个数
。
设
m
x
mx
mx 为左区间最大值,如果右区间的左区间最大值大于
m
x
mx
mx,说明右区间的右区间一定都大于
m
x
mx
mx,同时还要访问右区间的左区间,否则右区间的左区间都小于
m
x
mx
mx,直接访问右区间的右区间。
这里注意求右区间的右区间中大于
m
x
mx
mx 的个数不能直接用
c
n
t
(
右
右
)
cnt(右右)
cnt(右右),而要用 $cnt(右)-cnt(左),因为前者中可能包含小于
m
x
mx
mx 的项。
代码
struct segment_tree {
int l, r;
double mx;
int ans;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define mx(x) tree[x].mx
#define ans(x) tree[x].ans
}tree[maxn << 2];
void build(int p, int l, int r) {
if(l == r) {
l(p) = r(p) = l;
ans(p) = 0;
return;
}
l(p) = l, r(p) = r;
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
int query(int p, double maxx) {
if(mx(p) <= maxx) {
return 0;
}
if(l(p) == r(p)) {
return mx(p) > maxx;
}
double lmax = mx(p<<1);
if(lmax > maxx) {
// return ans(p<<1|1) + query(p << 1, maxx);
return ans(p) - ans(p << 1) + query(p << 1, maxx);
}
else {
return query(p << 1 | 1, maxx);
}
}
void pushup(int p) {
mx(p) = max(mx(p << 1), mx(p << 1 | 1));
ans(p) = ans(p << 1) + query(p << 1 | 1, mx(p << 1));
}
void change(int p, int id, double val) {
if(l(p) > id || r(p) < id) {
return ;
}
if(l(p) == id && r(p) == id) {
mx(p) = val;
ans(p) = 1;
return;
}
int mid = (l(p) + r(p)) >> 1;
if(id <= mid) {
change(p << 1, id, val);
}
else {
change(p << 1 | 1, id, val);
}
pushup(p);
}
void solve() {
int n, m;
cin >> n >> m;
build(1, 1, n);
while(m--) {
int x, y;
cin >> x >> y;
double val = y * 1.0 / x;
change(1, x, val);
cout << ans(1) << endl;
}
}