线段树的应用 -- 扫描线

场景:

求多个长方形任意摆放后的面积,(与底面保持水平)?

 如果通过任意两个长方形之间没有重叠的话或只存在两个长方形之间重叠,此时通过暴力枚举每个长方形就可。但如果存在多个长方形重叠,用常规的暴力方法很难去计算出其面积。

                                                      

那现在我们怎么去计算现在的面积?

我们可以发现因为长方形是正着摆,所以,无论有摆放多少个长方形,最终我们都可以把它分成若干个长方形相加,所以一个直接的思路就是按边对图形进行切割,再累计面积。但很明显实际上用代码去执行是非常复杂不好判断。

这里我们就开始介绍扫描线算法。本质的思路也跟切割一样。它先从一个方向(从左到右,从下到上都行),碰到一条边就累计面积。但和上面直接模拟切割的计算完全不一样。

现在输入长方形的左下的点和右上的点来确定一个长方形的位置。

总结一下思路,(我这边从下到上) 先统计x的值(要去重)从小到大排序,按x将区间分块,组合成一棵线段树,维护区间,一块的面积 = (y2 - y1) *(此时在x轴上占的长度)

我们可以发现扫描一个长方形时,总是先扫到长方形的下底边,再扫描上边。以上图为例,按y的值将面积分为三块,从下面开始扫描,这里已经按x的值建好了树。(下面数字代表上图中块的编号)

一,扫描到第一条边,此时的宽度值 4+5

二,扫描到第二条边,计算第一块面积,此时当前宽度+6

三,扫描到第三条边,计算第二块面积,此时当前宽度-4

四,扫描到第三条边,计算第三块面积,此时当前宽度-5-6;结束。

我们建线段树的目的主要就是要维护当前宽度。我们可以发现宽度的加减是和边的出入关系有关。

我们设置长方形的入边为1(底边)出边-1(顶边)

我们为这个线段树上的值打上标记。当这个区间经过2个入边那就是值为2。

以上例的第4块,第5块为例。

第一次,4,5经过入边,+1,值都为为1;

第二次,5经过入边+1,值为2;

第三次,4,5经过出边-1,4的值为0,5的值为1;

第四次,5 经过出边-1,值为0;

所以对于为此的当前宽度,我们只需要统计那些标记值为非零的区间。

参考:博客园用户 RakanX 学习笔记线段树

完整代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
struct ff
{
	ll y,lx,rx;//存边 
	ll in;
	ff(){}
	ff(ll lx_,ll rx_,ll y_,ll in_ )
	{
		lx = lx_,rx = rx_,y = y_,in = in_;
	}
	bool operator <(const ff &b)const
	{
		return y<b.y;
	}//比较规则,y越小的在前面 
}t[2000000];
ll kuan[2000000];ll flag[2000000];
ll sum[2000000];
ll find(ll tmp,ll n)
{	
	ll l = 1,r = n,mid;
	while(l<=r)
	{
		mid = (l+r)>>1;
		if(kuan[mid]==tmp) return mid;
		else if(kuan[mid]<tmp) l = mid+1;
		else r = mid-1;
	}
}//找到y边的左端点,右端点 
void push_up(ll u,ll l,ll r)//计算当前宽度
{
	if(flag[u]) sum[u] = kuan[r+1]-kuan[l];//非零,表示存在长度 
	else if(l==r) sum[u]=0;//相等 ==只有一块区间,而且不能取 
	else sum[u]=sum[2*u]+sum[2*u+1];//向上传递长度,累计长度 
}
//线段树维护的是区间不是点!!!!! 
void updata(ll u,ll l,ll r,ll tl,ll tr,ll c)
{
	if(tl<=l&&r<=tr)// 找到在目标区间的块 
	{
		flag[u]+=c;
		push_up(u,l,r);
		return ;
	}
	int mid = (l+r)>>1;
	if(tr<=mid)//目标区间在mid左边 
	updata(2*u,l,mid,tl,tr,c);
	else if(tl>mid)//目标区间在mid右边 
	updata(2*u+1,mid+1,r,tl,tr,c);
	else//目标区间包含mid 
	{
		updata(2*u,l,mid,tl,mid,c);
		updata(2*u+1,mid+1,r,mid+1,tr,c);
	}
	push_up(u,l,r);//更新结果 
}
int main() 
{
	ll n;
	scanf("%lld",&n);
	ll cnt = 0;
	ll x1,x2,y1,y2;
	for(ll i = 1;i<=n;++i)
	{
	scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
	t[++cnt] = ff(x1,x2,y1,1);//入边为1 
	kuan[cnt] = x1;
	t[++cnt] = ff(x1,x2,y2,-1);//出边为-1 
	kuan[cnt] = x2;}
	sort(kuan+1,kuan+cnt+1);//x值排序
	sort(t+1,t+cnt+1);//边排序
	unique(kuan+1,kuan+cnt+1);
	ll k;
	for(ll i = 1;i<=cnt;++i)
		if(kuan[i]>kuan[i+1])
		{
			k = i;
			break;
		}//去重,k为重新排序后最后一个不重复的下标。
	ll ans = 0;
	for(ll i = 1;i<cnt;++i)
	{	ll l = find(t[i].lx,k); //第一块的起点 
		ll r = find(t[i].rx,k)-1;//减一是为了保证区间块数 
		updata(1,1,k,l,r,t[i].in);//更新区间信息 
		ans+=sum[1]*(t[i+1].y - t[i].y);//面积求和
		
	}
	cout<<ans<<endl;
}
posted @ 2020-10-15 08:51  榉橡  阅读(278)  评论(0)    收藏  举报