洛谷4198 楼房重建 线段树

题目链接

题意:
你有 n n n个位置可以建楼,位置编号为 1 1 1 n n n,一开始所有位置都没有楼。每个位置的楼都可以看作一条垂直x轴的线段,我们从原点观察,当一栋楼上有一个点与原点连线上没有和其他楼的交点(交点是线段端点也不行)。有 m m m次操作,每次可以让一个位置的楼高度发生变化,可能高了也可能矮了。问你每次操作后从原点能看到多少栋楼。 n , m < = 1 e 5 n,m<=1e5 n,m<=1e5

题解:
可以发现一栋楼能被看到只要楼顶的那个端点能被看到即可。这是一个比较显然的事情,也比较符合生活。那么这个点和原点相连形成一条过原点的直线,这个点能被看到需要之前位置的楼的顶点与原点相连形成的直线的斜率都要大。换句话说,我们现在需要维护的是一个斜率,然后找最长单增斜率。

再看一下这个题变成了什么样子。其实相当于单点修改,每次修改完输出全局最长上升子序列。这个题相当于告诉了我们带修的最长上升子序列怎么做。

我之前想了维护出每个位置是不是在这个子序列上,然后分变高变矮讨论这么变它前面的和后面的答案会怎么办,哪些会进入答案序列,哪些会不在序列里。然后写了300多行,对拍后发现如果开头位置变小了,后面可能多出一堆新的合法的,这个东西维护不了。如果不能改开头位置,那这样可以一个log做。

于是就说一下题解的做法。还是考虑区间答案如何合并。我最开始觉得没法每个区间维护一个单调队列然后合并,于是觉得不可以这么做。但是这个题告诉我也有希望做。首先还是维护区间斜率的 m a x max max,以及一个区间的最长上升子序列的长度。当合并两个区间时,右区间对左区间没有影响,因为位置更靠后的无法挡住位置靠前的,于是左区间的贡献就是左区间的最长上升子序列长度。那么考虑右区间的长度。我们分别考虑右区间的左右子区间。如果右区间的左子区间的最大值大于了左区间最大值,那么右区间的右子区间的贡献对于新区间和对右区间的贡献相同。但是注意这个贡献不是它原本区间内部的最长上升子序列长度,因为可能被左子区间挡住一部分。实际贡献应该是右区间的答案减去右区间的左子区间的答案。那么这种情况我们递归到右区间在左子区间继续算贡献。否则的话我们直接递归到右子区间。当递归到长度为 1 1 1的区间时,我们判断一下区间内的 m a x max max是不是大于我们查询的 m a x max max即可。

然后我们来分析一下这么做的复杂度。我们每次单点修改只会经过线段树上的一条链。我们在链上的每一个点都会在合并时对右区间进行一次到叶子节点的递归来算答案,由于这个递归过程每次只会向左或向右的一个儿子递归,所以单次递归深度是 l o g log log的。那么我们不难发现,修改了 l o g log log个节点,每次修改合并时又递归了 l o g log log层,那么总复杂度是 l o g 2 n log^2n log2n的。

写起来其实挺好写的。但是不太容易想到这个问题是可以区间合并的。前几天在学李超树的时候还在想,这种先递归到所有要修改的节点,然后每个节点递归到底来更新当前节点的思想会不会以后用得到,结果马上就用到了,但是我没想到。仔细思考一下确实稍有不同,李超树是区间的修改,是先找到对应是 l o g log log个区间,但是这个题是一条链上的 l o g log log个点。一个不大的变化。主要是觉得那个信息不可以合并,所以没做对这个题。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m;
struct node
{
	int l,r,s;
	double mx;
}tr[800010];
inline void build(int rt,int l,int r)
{
	tr[rt].l=l;
	tr[rt].r=r;
	tr[rt].s=0;
	tr[rt].mx=0.0;
	if(l==r)
	return;
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
}
inline int query(int rt,double mx)
{
	int l=tr[rt].l,r=tr[rt].r;
	if(l==r)
	{
		if(tr[rt].mx>mx)
		return 1;
		else
		return 0;
	}
	if(tr[rt<<1].mx>mx)
	return query(rt<<1,mx)+tr[rt].s-tr[rt<<1].s;
	else
	return query(rt<<1|1,mx);
}
inline void update(int rt,int id,int x)
{
	int l=tr[rt].l,r=tr[rt].r;
	if(l==r)
	{
		tr[rt].s=1;
		tr[rt].mx=1.0*x/id;
		return;
	}
	int mid=(l+r)>>1;
	if(id<=mid)
	update(rt<<1,id,x);
	else
	update(rt<<1|1,id,x);
	tr[rt].mx=max(tr[rt<<1].mx,tr[rt<<1|1].mx);
	tr[rt].s=tr[rt<<1].s+query(rt<<1|1,tr[rt<<1].mx);
}
int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		update(1,x,y);
		printf("%d\n",tr[1].s);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值