HDU - 1828 -Picture (线段树+扫描线+求矩形围成图形周长)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1828

说完了矩形面积,矩形周长的方法自然是类似的,但是周长的计算却更复杂些,看这张图:

 

周长可以分成两部分计算,横线和竖线,如图将所有彩色的横线加起来就是横向的所有长度了

然后可以采用竖直方向的扫描线将竖线的所有长度求出来

那么怎么计算横线的长度呢?

横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】

想想为什么要加绝对值(提示:下边--删除,上边--插入)

这样用自下而上和从左往右的两次扫描即可得到答案。

但是,这样的方法显得有些笨,有没有更高端的方法呢?

再看一张图:

 

看出什么了吗?这张图在上面那张图的基础上多了几条竖线。

我的意思是说,我们可以只做一次自下而上的扫描就把横线竖线都算出来!

竖线的算法和上面说的方法一样:【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】

竖线要怎么计算?

首先我们现在改一下线段树保存的属性,我们用如下信息记录线段树的节点:

1. l , r : 该节点代表的线段的左右端点坐标

2.len : 这个区间被覆盖的长度(即计算时的有效长度)

3.s : 表示这个区间被覆盖了几次

4. lc , rc : 标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)

5.num :这个区间有多少条线段(这个区间被多少条线段覆盖)

这里的num涉及到竖线的计算,故解释一下,举几个例子:

   若区间[0,10]被[1,2][4,5]覆盖,则num = 2

   若区间[0,10]被[1,3][4,5]覆盖,则num = 1(两区间刚好连在一起)

   若区间[0,10]被[1,5][2,6]覆盖,则num = 1(两区间连起来还是一段)

然后就可以计算竖线了:

竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*2*num

乘2是因为每条线段有两个端点;

看上图中棕色线段的竖线有4条,因为棕色的横线由2条线段组成

白色线段的竖线只有2条,因为白色的横线由1条线段组成(虽然这1条线段是由许多线段组合而成,但它依旧只算1条线段)

这样,依旧只扫一次就可以算出周长。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
int n;
const int INF=5e3+5;
const int Max=2e4+2000;
struct node{
	int from;
	int to;
	int h;
	int d;
}g[2*INF];
struct Node{
	int right;
	int left;
	int sum;//覆盖区间的长度 
	bool ll;//标记这个节点的左右两个端点是否被覆盖
	bool rr;//(0表示没有,1表示有) 
	int mark;//标记区间是否被覆盖 
	int num;//这个区间有多少条线段(这个区间被多少条线段覆盖)
}f[4*Max];
bool cmp(node p1,node p2)
{
	return p1.h <p2.h ;
}
void build(int ans,int l,int r){
	f[ans].left =l;f[ans].right =r;
	f[ans].mark =f[ans].num =f[ans].sum =0;
	f[ans].ll =f[ans].rr =0;
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(ans<<1,l,mid);
	build(ans<<1|1,mid+1,r);
}
void pushup(int ans){
	if(f[ans].mark ){//整个区间被覆盖 
		f[ans].sum =f[ans].right -f[ans].left +1;
		f[ans].rr =1 ;
		f[ans].ll =1 ;
		f[ans].num =2;
	}
	else if(f[ans].left ==f[ans].right ){ 
		f[ans].sum =0;
		f[ans].num =0;
		f[ans].ll =f[ans].rr =0;
	}
	else{
		f[ans].sum =f[ans<<1].sum +f[ans<<1|1].sum ;
		f[ans].num =f[ans<<1].num +f[ans<<1|1].num ;
		f[ans].ll =f[ans<<1].ll ;//和左儿子共左端点 
		f[ans].rr =f[ans<<1|1].rr;//和右儿子共右端点 
		if(f[ans<<1].rr&&f[ans<<1|1].ll ){//此时两段合为一段 
			f[ans].num -=2;
		}
	}
}
void update(int ans,int l,int r,int x)
{
	if(f[ans].left>=l&&f[ans].right <=r)
	{
		f[ans].mark +=x;
		pushup(ans);
		return;
	}
	int mid=(f[ans].left +f[ans].right )>>1;
	if(l<=mid){
		update(ans<<1,l,r,x);
	}
	if(r>mid){
		update(ans<<1|1,l,r,x);
	}
	pushup(ans);
}
int main()
{
    while(scanf("%d",&n)!=EOF)
	{
		if(n==0)
		{
			printf("0\n");continue;
		}
	    int i,j;
	    int mx=-10000,mi=10000;
		for(i=1,j=0;i<=n;i++)
		{
		    int x1,y1,x2,y2;
			scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
			mx=max(mx,x2);
			mi=min(mi,x1);
		    g[++j].d =1;g[j].from =x1;
			g[j].to =x2;g[j].h =y1;
			g[++j].d =-1;g[j].from =x1;
			g[j].to =x2;g[j].h =y2;
		}
		sort(g+1,g+1+j,cmp);//数据不大,不用离散化 
		build(1,mi,mx-1);//n个x点之间有(n-1)个区间
		int sum=0;
		int last=0;
		for(i=1;i<=j;i++)//注意这里和计算面积不一样,要取到最上面的那条边 
		{
			if(g[i].from <g[i].to )update(1,g[i].from ,g[i].to-1 ,g[i].d );
			sum+=(g[i+1].h-g[i].h  )*(f[1].num );//竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*num 
			sum+=abs(last-f[1].sum );//横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
			last=f[1].sum ;//每次都要更新上一次总区间覆盖的长度 
		}
		printf("%d\n",sum);
	}	
    return 0;	
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值