Atlantis
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 12377 Accepted Submission(s): 5195
点击打开题目链接
The input file is terminated by a line containing a single 0. Don’t process it.
Output a blank line after each test case.
2 10 10 20 20 15 15 25 25.5 0
Test case #1 Total explored area: 180.00
题目意思:
给出n个矩形,求他们面积的并。
解题思路:看了别人的东西,然后回来研究了一下。
解这个题目用知识是线段树。感觉很神奇。思想真的不知道怎么来说,看了别人的也没觉得就都讲的很好,就直接晒代码,再让
大脑跟着运行一下。下面是一下午的收获。
解决题目的关键是先定位矩形的横坐标x,然后取其区间为(y1,y2).并将所有的纵坐标存起来放在数组中。排序一下,找出最小纵坐标和
最大纵坐标,这个最大的线段区间就是线段树的根,然后根据存放纵坐标的数组构建线段树。(感觉这个很难去表述思路,只可意会,不可
言传的感觉,明白了又说不出)。。。
上代码,和自己用脑运行代码的过程吧。代码看完不要走开哦,后面有参照代码的求解思路。
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
const int MAXN = 110;
using namespace std;
int n;
double Y[2*MAXN];
struct Line
{
double x; ///横坐标的值
double y1,y2; ///纵坐标的区间
int flag; ///flag等于1时,代表它是长方形左边,flag等于-1时,代表它是长方形右边
}line[2*MAXN];
struct NODE ///线段树节点类型
{
double x; ///最新到过该节点的线对应的x(横坐标的值)
double left,right; ///左边界,右边界
bool sign; ///用来标记该节点是否叶子节点
int cover; ///覆盖值
}node[MAXN<<3];
///这里不能使MAXN左移两位,因为有N个矩形,对应2*N个线段,则应该是2*N左移2位。
bool cmp(struct Line L1,struct Line L2)
{
return L1.x < L2.x;
}
void Build(int rt,int l,int r) ///l,r是Y数组的下标
{
node[rt].left = Y[l];
node[rt].right = Y[r];
node[rt].x = -1;
node[rt].sign = false; ///flase说明这个节点不是叶子节点
node[rt].cover = 0;
if(l+1 == r)
{
node[rt].sign = true; ///叶子节点
return;
}
int mid = (l+r)>>1;
Build(rt<<1,l,mid); ///递归构建左子树
Build(rt<<1|1,mid,r); ///递归构建右子树
}
///参数含义,rt为节点,line_x代表当前线对应的横坐标,l下界,r上界
///flag代表当前的线是长方形的左侧的边还是右侧的边
double Calculate(int rt,double line_x,double l,double r,int flag)
{
if(r<=node[rt].left || l>=node[rt].right)
return 0;
if(node[rt].sign) ///代表其是叶子节点
{
if(node[rt].cover>0)
{
double pre = node[rt].x;
double ans = (line_x-pre)*(node[rt].right-node[rt].left);
node[rt].x = line_x;
node[rt].cover += flag;
return ans;
}
else
{
node[rt].x = line_x;
node[rt].cover += flag;
return 0;
}
}
double ans1 = Calculate(rt<<1,line_x,l,r,flag);
double ans2 = Calculate(rt<<1|1,line_x,l,r,flag);
return ans1+ans2;
}
int main()
{
int Case = 0;
double x1,y1,x2,y2;
while(~scanf("%d",&n))
{
if(n == 0)
break;
int i,j;
j = 0;
for(i = 0; i < n; i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
Y[j] = y1;
line[j].x = x1; ///长方形左侧的边
line[j].y1 = y1;
line[j].y2 = y2;
line[j].flag = 1;
j++;
Y[j] = y2;
line[j].x = x2; ///长发型右侧的边
line[j].y1 = y1;
line[j].y2 = y2;
line[j].flag = -1;
j++;
}
sort(Y,Y+j); ///对纵坐标进行从小到大排序。
sort(line,line+j,cmp); ///对线的横坐标进行从小到大排序
Build(1,0,j-1); ///构建线段树
double union_area = 0;
for(i = 0; i < j; i++)
{
union_area += Calculate(1,line[i].x,line[i].y1,line[i].y2,line[i].flag);
}
printf("Test case #%d\n",++Case);
printf("Total explored area: %.2lf\n\n",union_area);
}
return 0;
}
以测试数据为例,求解代码的运行过程。
首先要构建线段树,构建好的线段树和排好序的直线如下图所示,其flag值代表这条线段是矩形的左侧边(用1表示)还是右侧的边(用-1)表示。
接下来按照代码,考虑第一条线段。就是图中红色线段。递归过程其在线段树中的走向如树种红色线段所示。
接下来考虑第2条线段,就是图中的蓝色线段,递归过程在线段树中的走向如蓝色线段所示。
注意注意注意!!!!!。上面图中的写错了,应该是节点2和节点4的x的值变为10,其对应的flag值都等于1.
再考虑第三条线段,就是坐标系中绿色的线段。其递归过程在线段树中的走向如绿色线段所示。
然后考虑最后一条线段,褐色线段,其走向如图褐色线段所示:
最后总结一下,每次调用求的面积图示: