P4198 楼房重建 线段树 *

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值