题意:
给出n个矩形的左下角和右上角坐标,求这n个矩形所构成的面积
思路:
线段树扫描线
这是第一次做到线段树扫描线,刚开始也不懂
如果不懂,可以看:
http://www.cnblogs.com/scau20110726/archive/2013/04/12/3016765.html
和 http://www.faceye.net/search/69289.html
我是看第一个链接弄懂的 然后学习了第二位的方法
代码上也有比较详细的注释,想可以帮到大家
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 205;
struct Line{
double x;//存的是线段的x坐标
double y1,y2;//存的是线段的下、上两个端点
double f;//f是用来记录重叠情况的,可以根据这个来计算node中的c
}line[maxn*2];
//把一段段平行于y轴的线段表示成数组,
//x是线段的x坐标,y1 y2是线段对应的下端点和上端点
//一个矩形,左侧的边的f = 1,右侧的边 f= -1
//f是用来记录重叠情况的,可以根据这个来计算node中的c
double y[maxn*2];//用来记录y坐标,排序之后用来给线段树中的左右实际结点赋值,见代码build部分
//线段树的节点总数,大概要是线段个数的 4 倍(线段树和被操作的数据之间总是四倍关系)
const int maxnode = maxn << 2;
struct IntervalTree{
double ll[maxnode],rr[maxnode],lf[maxnode],rf[maxnode],cnt[maxnode];
//ll[]和rr[] 是用来标记线段树某一个区间的 经过离散化之后的 可以投影到实际坐标的 那个左右整点代号
//lf rf 是用来记录真正的左右坐标的,用来计算该区间的长度
int c[maxnode];//c是用来标记重叠情况的
void build(int o, int l, int r){//递归建线段树
ll[o] = l, rr[o] = r;
cnt[o] = c[o] = 0;
lf[o] = y[l], rf[o] = y[r];
int mid = (l+r)>>1;
int lc = o << 1, rc = o << 1 | 1;
if(l + 1 == r) return;
build(lc, l, mid);
build(rc, mid, r);
}
void calen(int o){//用来计算区间o中的 线段被覆盖的长度
if(c[o] > 0){
cnt[o] = rf[o] - lf[o];
return;
}
if(ll[o] + 1 == rr[o]) cnt[o] = 0;
else{
cnt[o] = cnt[o<<1] + cnt[o<<1|1];
}
}
void update(int o, Line e){//新加入一条线段后 更新线段树
double y1 = e.y1, y2 = e.y2;
if(lf[o] == y1 && y2 == rf[o]){
c[o] += e.f;
calen(o);
return;
}
int lc = o << 1, rc = o<<1|1;
if(y2 <= rf[lc]) update(lc, e);
else if(y1 >= lf[rc]) update(rc, e);
else{
Line tmp = e;
tmp.y2 = rf[lc];
update(lc, tmp);
tmp = e;
tmp.y1 = lf[rc];
update(rc, tmp);
}
calen(o);
}
}tree;
bool cmp(Line a, Line b){
return a.x < b.x;
}
int main(){
int n;
int cas = 0;
while(scanf("%d",&n) != EOF){
if(n == 0) break;
double y1,y2,x1,x2;
int t = 1;
for(int i = 1; i <= n; i++){
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
//这是一个处理和保存矩形左右两条边,并且对坐标进行离散化的过程
//一个矩形 对应的是两条线段,左面一条(f = 1)右面一条(f = -1)
line[t].x = x1;
line[t].y1 = y1;
line[t].y2 = y2;
line[t].f = 1;
y[t] = y1;
t++;
line[t].x = x2;
line[t].y1 = y1;
line[t].y2 = y2;
line[t].f = -1;
y[t] = y2;
t++;
}
sort(y+1, y+t);//对y坐标按照从小到大的顺序排序,这是为了建立线段树,排完序才能离散地分成许多个区间
sort(line+1, line+t, cmp);//从左到右扫描的话,应该保证在左边的线段先被扫到,
//这里的排序就是让左边的线段在前面(按照x坐标从小到大排序)
double res = 0;
tree.build(1,1,t-1);
//每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边
//的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,
//就能得到最后的面积。
tree.update(1,line[1]);//先扫地一条边
for(int i = 2; i < t; i++){
res += tree.cnt[1] * (line[i].x - line[i-1].x);//用 高度*覆盖长度
tree.update(1, line[i]);//扫描当前边
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",++cas,res);
}
return 0;
}