扫描线+重边处理 或 直接线段树维护 楼房(洛谷 P1382 )

P1382 楼房

题目描述:

地平线(x轴)上有n个矩(lou)形(fang),用三个整数h[i],l[i],r[i]来表示第i个矩形:矩形左下角为(l[i],0),右上角为(r[i],h[i])。地平线高度为0。在轮廓线长度最小的前提下,从左到右输出轮廓线。
下图为样例2。
在这里插入图片描述

输入格式:

第一行一个整数n,表示矩形个数
以下n行,每行3个整数h[i],l[i],r[i]表示第i个矩形。

输出格式:

第一行一个整数m,表示节点个数
以下m行,每行一个坐标表示轮廓线上的节点。从左到右遍历轮廓线并顺序输出节点。第一个和最后一个节点的y坐标必然为0。


初看这题,并不知道和线段树是什么关系;在看了题解以后,才知道可以用线段树维护每个点的最大高度,就是整个图形的轮廓线;但是细节真的很多。首先,我们如果用区间数组存x坐标来做,x的范围是10^-9 到10^9,肯定爆内存,所以第一步就是离散化x坐标,具体怎么离散化看代码自己模拟一下就知道了;离散化完了以后,我们就要区间修改最大值了;这道题的最关键之处在于,我们要知道一个轮廓线肯定有一个横线,那么这个横线的两头的y坐标都相同,那么我们知道了一头的y坐标,另一头也就知道了,并且如果两个y坐标不相等,那么轮廓线肯定发生变化;那么我们求区间最大值时,只要更新到右端点-1就行,最后输出按竖线的方式输出;
代码:

#include<bits/stdc++.h>
using namespace std;
int h[200000],L[200000],R[200000],a[400000],b[400000];
int f1[200000],f2[200000];
int x,y,z;
struct Node{
	int l,r,w,f;
}tree[800000];
inline void build(int k,int ll,int rr){
	tree[k].l=ll,tree[k].r=rr,tree[k].f=0;
	if(ll==rr){
		tree[k].w=0;
		return;
	}
	int m=(ll+rr)>>1;
	build(k<<1,ll,m);
	build(k<<1|1,m+1,rr);
}
inline void pd(int k){
	if(tree[k].f){
		tree[k<<1].f=max(tree[k].f,tree[k<<1].f);
		tree[k<<1|1].f=max(tree[k].f,tree[k<<1|1].f);
		tree[k<<1].w=max(tree[k<<1].w,tree[k].f);
		tree[k<<1|1].w=max(tree[k<<1|1].w,tree[k].f);
		tree[k].f=0;
	}
}
inline void change(int k){
	if(tree[k].l>=x&&tree[k].r<=y){
		tree[k].w=max(tree[k].w,z);
		tree[k].f=max(tree[k].f,z);
		return; 
	}
	pd(k);
	int m=(tree[k].l+tree[k].r)>>1;
	if(x<=m) change(k<<1);
	if(y>m) change(k<<1|1);
	tree[k].w=max(tree[k<<1].w,tree[k<<1|1].w);
}
inline int ask(int k){
	if(tree[k].l==tree[k].r){
		return tree[k].w;
	}
	pd(k);
	int m=(tree[k].l+tree[k].r)>>1;
	if(x<=m) ask(k<<1);
	else ask(k<<1|1);
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&h[i],&L[i],&R[i]);
		a[i*2-1]=L[i],a[i*2]=R[i];//第一步离散化,手动模拟一下就知道了 
	}
	sort(a+1,a+2*n+1);
	b[1]=a[1];
	int ans=1;//这个是1,不是0 
	for(int i=2;i<=2*n;i++){//第二步离散化,相当于把a数组去重 
		if(a[i]!=a[i-1]) b[++ans]=a[i];
	}
	build(1,1,ans);
	for(int i=1;i<=n;i++){
		x=lower_bound(b+1,b+ans+1,L[i])-b;
		y=lower_bound(b+1,b+ans+1,R[i])-b-1;//只需更新右边界ans-1就行,ans不用更新 
		//cout<<x<<" "<<y<<endl;
		z=h[i];
		change(1);//更新区间最大y坐标值 
	}
	int sum=0;//所有横线右端点的数量,非常重要
	for(int i=1;i<=ans;i++){
		f1[i]=b[i];//表示一个点的x坐标
		x=i;
		f2[i]=ask(1);//表示一个点的最高y坐标 
		//cout<<f2[i]<<endl;
		if(f2[i]!=f2[i-1]) sum++;//仔细想想,一个横线的右端点只要一个,并且不相等 
	}
	printf("%d\n",sum*2);//输出总端点。sum*2
	for(int i=1;i<=ans;i++){
		if(f2[i]!=f2[i-1]){//跟上面一个原理 
			printf("%d %d\n",f1[i],f2[i-1]);
			printf("%d %d\n",f1[i],f2[i]); 
		}
	}
	return 0;
}

更新一个扫描线做法,基本思路和模板一样,但是要特别注意,模板在有重边时求面积是没有任何问题的,但是这个轮廓线却有问题;

当入边有重边时,只要计算 Y 2 Y_2 Y2最高的点就行,所以排序特别关键,一定要保证 Y 2 Y_2 Y2排在前面;当出边有重边时,保证 Y 2 Y_2 Y2高的排在后面,跟入边相反;

代码:

#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=100100;
const int M=50100;
const LL mod=10007;
int n,h[N],l[N],r[N],a[N],cnt,tot,val[N];
vector<pa>ans,ve;
struct D{
	int Y1,Y2,X,f;
}du[N*2];
bool cmp(D p,D q){
	if(p.X==q.X){
		if(p.f==q.f){
			if(p.f==1) return p.Y2>q.Y2;
			else if(p.f==-1) return p.Y2<q.Y2;
		}
		return p.f>q.f;
	}
	return p.X<q.X;
}
struct Node{
	int l,r,lz,len;
}tr[N*8];
void build(int l,int r,int k){
	tr[k].l=l,tr[k].r=r;
	if(l==r) return;
	int d=(l+r)>>1;
	build(l,d,ls);
	build(d+1,r,rs);
}
void pp(int k){
	if(tr[k].lz) tr[k].len=val[tr[k].r+1]-val[tr[k].l];
	else if(tr[k].l==tr[k].r) tr[k].len=0;
	else tr[k].len=tr[ls].len+tr[rs].len;
} 
void update(int l,int r,int w,int k){
	if(tr[k].l>=l&&tr[k].r<=r){
		tr[k].lz+=w;
		pp(k);
		return;
	}
	int d=(tr[k].l+tr[k].r)>>1;
	if(l<=d) update(l,r,w,ls);
	if(r>d) update(l,r,w,rs);
	pp(k);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d%d",&h[i],&l[i],&r[i]),a[i]=h[i];
	sort(a+1,a+n+1);
	cnt=unique(a+1,a+n+1)-a-1;
	for(int i=1;i<=n;i++){
		int p=lower_bound(a+1,a+cnt+1,h[i])-a;
		val[p]=h[i];
		du[++tot].f=1,du[tot].X=l[i],du[tot].Y1=0,du[tot].Y2=p;
		du[++tot].f=-1,du[tot].X=r[i],du[tot].Y1=0,du[tot].Y2=p;
	}
	sort(du+1,du+tot+1,cmp);
	build(0,cnt-1,1);
	for(int i=1;i<=tot;i++){
		int mx=tr[1].len;
		update(du[i].Y1,du[i].Y2-1,du[i].f,1);
		if(du[i].f==1){//入边 
			if(val[du[i].Y2]>mx){
				ans.push_back(pa(du[i].X,mx));
				ans.push_back(pa(du[i].X,val[du[i].Y2]));
			}
		}
		else{//出边 
			if(val[du[i].Y2]>=mx&&val[du[i].Y2]!=tr[1].len){
				ans.push_back(pa(du[i].X,val[du[i].Y2]));
				ans.push_back(pa(du[i].X,tr[1].len));
			}
		} 
	}
	ve=ans;
	printf("%d\n",(int)ve.size());
	for(int i=0;i<ve.size();i++) printf("%d %d\n",ve[i].first,ve[i].second);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值