题目大意


平面上有一些“回型”图案,每一个“回型”是由一个大矩形中间挖去一个小矩形构成,大小矩形的四边都平行于坐标轴。

现在有n个不同大小的“回型”图案,他们可能互相重叠,请求出被他们所覆盖的平面的总面积。

思路


首先,将每个一个回型划分为四个矩形。将每个矩形的与x轴平行的边按离x轴距离由近及远顺序插入线段树中(底边插入,顶边删除)。

在对每条线段执行插入之前,先计算下当前线段与上一条线段之间的覆盖面积。用tree[1].len*当前线段与前一个线段之间的距离(高度差),tree[1].len表示图形的宽。

当遍历完所有线段时,就会得出覆盖面积。

代码


#include<iostream>
#include<stdio.h>
using namespace std;
#define MAXSIZE 50000
#define N 50000
struct node
{
	int total; 
	//total表示该节点所表示的区间被线段覆盖的次数 
	int left;
	int right;
	int len;
	//该区间内,线段的长度。 
}tree[MAXSIZE*3];
struct line
{
	int pos;
	//pos表示y轴坐标 
	int left;
	int right;
	bool top;
	//判断该线段是否是矩形的上边界。 
}edge[N*8];
int total_edge,n;
//一共total_edge个线段,n个回型。 
void create(int root,int left,int right)
//建树,明确每个节点对应的区间 
{
	tree[root].left=left;
	tree[root].right=right;
	if(left==right)
		return ;
	int middle=(left+right)/2;
	create(2*root,left,middle);
	create(2*root+1,middle+1,right);
}
void updata(int root,int left,int right,int val)
//将线段插入或删除。 
{
	if(tree[root].left>right||tree[root].right<left)
		return ;
	else if(left<=tree[root].left&&tree[root].right<=right)
		tree[root].total+=val;
	else
	{
		updata(2*root,left,right,val);
		updata(2*root+1,left,right,val);
	}
	if(tree[root].total>0)
		tree[root].len=tree[root].right-tree[root].left+1;
	else if(tree[root].right==tree[root].left)
		tree[root].len=0;
	else
		tree[root].len=tree[2*root].len+tree[2*root+1].len;
}
void add_edge(int pos,int left,int right,bool top)
//记录一条线段的信息 
{
	if(left>right)
		return;
	edge[total_edge].pos=pos;
	edge[total_edge].left=left;
	edge[total_edge].right=right;
	edge[total_edge].top=top;
	total_edge++;
}
int cmp(const void* m,const void* n)
//将线段按照y轴坐标由小到大排序。 
{
	return (*(line*)m).pos-(*(line*)n).pos;
}
void init()
//初始化及输入 
{
	total_edge=0;
	for(int i=1;i<MAXSIZE*3;i++)
	{
		tree[i].len=0;
		tree[i].total=0;
	}
	for(int i=0;i<n;i++)
	{
		int x1,y1,x2,y2,x3,y3,x4,y4;
		scanf("%d%d%d%d%d%d%d%d",&x1,&y1,&x2,&y2,&x3,&y3,&x4,&y4);
		add_edge(y1+1,x1+1,x3,false);
		add_edge(y2+1,x1+1,x3,true);
		add_edge(y1+1,x3+1,x4,false);
		add_edge(y3+1,x3+1,x4,true);
		add_edge(y4+1,x3+1,x4,false);
		add_edge(y2+1,x3+1,x4,true);
		add_edge(y1+1,x4+1,x2,false);
		add_edge(y2+1,x4+1,x2,true);
	}
}
int main()
{
	create(1,1,MAXSIZE+1);
	while(scanf("%d",&n)&&n)
	{
		init();
		qsort(edge,total_edge,sizeof(edge[0]),cmp);
		long long total_size=0;
		for(int i=0,pre=1;i<total_edge;i++)
		{
			total_size+=(long long)tree[1].len*(edge[i].pos-pre);
			//这里注意,tree[1].len必须转化为long long型 
			updata(1,edge[i].left,edge[i].right,edge[i].top?-1:1);
			pre=edge[i].pos;
		}
		printf("%I64d\n",total_size);
	}
}

后记


此题前前后后做了3天。主要原因是对线段树成段更新的不熟练(对延时标记处理得不够老练)。做此题之前只会成段赋值,不会成段增减。因此特地找到POJ 3468写了个成段增减的模板,为做此题打基础。(虽然后来发现此题与成段增减的关系貌似不大)

另外在调试此题的过程中,还发现了原先成段赋值模板(根据HDU 1698写的模板)的一处设计缺陷。在此模板中,将树中节点的int型延迟标记初始化为0,并根据延迟标记是否不为0来判断节点是否被更改。但这就产生了新的问题,若我想要将一个区间赋值为0该怎么办?此时延迟标记被赋值为0,节点会被误判为未更改。

历经千辛万苦,最终发现此题并不是什么成段增减。最终思路还是通过看作者题解得来的。