题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1542
首先附上大佬们的博客:
http://www.cnblogs.com/scau20110726/archive/2013/03/21/2972808.html
http://blog.csdn.net/xingyeyongheng/article/details/8927732#
题目大意:
求所有矩形覆盖的面积。
分析:
第一道关于扫描线的题目。首先解释一下扫描线是什么,就是一条你脑子中想象出来的一条虚拟的线。可以从下
往上扫(从上往下扫),也可以从左往右扫(从右往左扫),想怎么扫就得写什么样的代码。如果是竖直方向上扫描,
则离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的。
其实第一次接触扫描线的题,问题肯定就是这个面积是怎么算的?一句话概括,就是这个矩形面积不是一个一个算的,
是每次所有矩形在某一层高度内的面积之和,然后计算每一层的。那层是什么东西?就是矩形上下边确定的高度。
扫描之前还需要一个工作,就是保存好所有矩形的上下边,并且按照他们的高度进行排序,另外如果是上边我们给
他一个值-1, 下边给他一个值1,用一个结构体保存所有上下边。
struct segment
{
double l, r, h;//l,r表示这条上下边的左右坐标,h是这条边所处的高度
int f;//所赋的值,下边1,上边-1
}ss[2*maxn];
接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。
每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积
(这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮助)
从这个图,也可以感受到,就好比一个畸形的容器,往里面倒水,从最下面往上面涨,被水淹过的部分其实就是我们要求的面积
其实这个线段树就是以所有矩形上下边的左右端点建立的,维护的是一个区间的长度。
最后要注意一下一个很重要的技巧:
这里注意下扫描线段时r-1:int r = binary(ss[i].r, 0, m-1) - 1;
计算底边长时r+1:segTree[rt].len = pos[segTree[rt].r+1] - pos[segTree[rt].l];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=pos[mid]-pos[left]+pos[right]-pos[mid+1]
这样得到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如pos[3]=1.2,pos[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 110;
struct segment
{
double l, r, h;//l,r表示这条上下边的左右坐标,h是这条边所处的高度
int f;//-1为下边界,1为上边界
}ss[2*maxn];
struct Node//线段树节点
{
int l, r, cnt;//cnt为该节点被覆盖的情况
double len;//该区间被覆盖的总长度
}segTree[maxn<<3];
double pos[maxn*2];
int nums;
bool cmp(struct segment p, struct segment q)
{
return p.h < q.h;
}
void build(int l, int r, int rt)
{
segTree[rt].l = l;
segTree[rt].r = r;
segTree[rt].cnt = 0;
segTree[rt].len = 0;
if(l == r)
return ;
int mid = (segTree[rt].l + segTree[rt].r) >> 1;
build(l, mid, rt<<1);
build(mid+1, r, (rt<<1)|1);
}
void get_len(int rt)
{
if(segTree[rt].cnt)//非0,已经被整段覆盖
segTree[rt].len = pos[segTree[rt].r+1] - pos[segTree[rt].l];
else if(segTree[rt].l == segTree[rt].r)//已经不是一条线段,只是一个点
segTree[rt].len = 0;
else//是一条线段但是又没有整段覆盖,那么只能从左右孩子的信息中获取
segTree[rt].len = segTree[rt<<1].len + segTree[(rt<<1)|1].len;
}
void update(int l, int r, int val, int rt)
{
if(segTree[rt].l == l && segTree[rt].r == r)//目标区间
{
segTree[rt].cnt += val;//更新这个区间被覆盖的情况
get_len(rt);//更新这个区间被覆盖的总长度
return ;
}
int mid = (segTree[rt].l + segTree[rt].r) >> 1;
if(r <= mid)
update(l, r, val, rt<<1);
else if(l > mid)
update(l, r, val, (rt<<1)|1);
else
{
update(l, mid, val, rt<<1);
update(mid+1, r, val, (rt<<1)|1);
}
get_len(rt);//计算该区间被覆盖的总长度
}
int binary(double key, int low, int high)
{
int mid;
while(low <= high)
{
mid = (low + high) >> 1;
if(pos[mid] == key)
return mid;
else if(key < pos[mid])
high = mid - 1;
else
low = mid + 1;
}
return -1;
}
int main()
{
int Case = 0;
int n;
while(scanf("%d", &n) != EOF && n)
{
nums = 0;
for(int i = 0; i < n; i++)
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
ss[nums].l = x1;ss[nums].r = x2;ss[nums].h = y1;ss[nums].f = 1;//记录下边界的信息
ss[nums+1].l = x1;ss[nums+1].r = x2;ss[nums+1].h = y2;ss[nums+1].f = -1;//记录上边界的信息
pos[nums] = x1;pos[nums+1] = x2;//记录横坐标
nums += 2;
}
sort(ss, ss+nums, cmp);//横线按纵坐标升序排序
sort(pos, pos+nums);//横坐标升序排序
int m = 1;
for(int i = 1; i < nums; i++)
if(pos[i] != pos[i-1])//去重
pos[m++] = pos[i];
build(0, m-1, 1);//离散化后的区间就是[0,m-1],以此建树
double ans = 0;
for(int i = 0; i < nums-1; i++)//拿出每条横线并且更新
{
int l = binary(ss[i].l, 0, m-1);
int r = binary(ss[i].r, 0, m-1) - 1;
update(l, r, ss[i].f, 1);//用这条线段去更新
ans += (ss[i+1].h - ss[i].h) * segTree[1].len;
}
printf("Test case #%d\n", ++Case);
printf("Total explored area: %.2f\n\n", ans);
}
return 0;
}