亚特兰蒂斯【线段树+扫描线+离散化】
POJ1151
、ACwing247
题目:
有几个古希腊书籍中包含了对传说中的亚特兰蒂斯岛的描述。
其中一些甚至包括岛屿部分地图。
但不幸的是,这些地图描述了亚特兰蒂斯的不同区域。
您的朋友 Bill 必须知道地图的总面积。
你自告奋勇写了一个计算这个总面积的程序。
输入格式:
输入包含多组测试用例。
对于每组测试用例,第一行包含整数 nn,表示总的地图数量。
接下来 n 行,描绘了每张地图,每行包含四个数字 x1,y1,x2,y2(不一定是整数),(x1,y1) 和 (x2,y2) 分别是地图的左上角位置和右下角位置。
注意,坐标轴 x 轴从上向下延伸,y 轴从左向右延伸。
注意这里的提示,也就是我们正常理解的左下角和右上角
当输入用例 n=0 时,表示输入终止,该用例无需处理。
输出格式:
每组测试用例输出两行。
第一行输出 Test case #k
,其中 k 是测试用例的编号,从 1 开始。
第二行输出 Total explored area: a
,其中 a 是总地图面积(即此测试用例中所有矩形的面积并,注意如果一片区域被多个地图包含,则在计算总面积时只计算一次),精确到小数点后两位数。
在每个测试用例后输出一个空行。
数据范围:
1≤n≤10000,
0≤x1<x2≤100000,
0≤y1<y2≤100000
注意,本题 n 的范围上限加强至 10000。
样例输入:
2
10 10 20 20
15 15 25 25.5
0
样例输出:
Test case #1
Total explored area: 180.00
思路:
如果y轴离散化后数组为ys[5.1,6.2,8.5,10.8]
我们假设线段树内的结点[1,1]、[2,2]的父节点是[1,2],若要[1,2]这个结点的len能够映射离散化区间里面的8.5、6
2。但是如果根节点是[0,2],要查询[1,2]的长度len,就会将[0,2]分成[0,1]和[2,2],这样就把要求的区间砍断了。
所以采用另外一种映射方法。将区间[0,0]映射到5.16.5,[1,1]映射到6.28.5。那么线段树的最小单位就是一个长度而不是一个点了,也就是:[l,r]的长度=ys[r+1]-ys[l]。
主要就是离散化过后让线段树的每个结点的y轴不存在交叉,然后y轴的宽度累加乘以x扫过的距离,再对该乘积进行累加,就是最终的面积了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e4+5;
int n;
//--------------------------------------
struct Node{
int l,r; //存储区间
int count; //当前区段被有效覆盖的次数
double len; //当前区段内的有效长度
} tree[N << 3]; //存储线段树
//--------------------------------------
struct edge{
double x,y1,y2; //三个坐标
int k; //1表示是矩形左边的线,-1表示是矩形右边的线
} E[N << 1]; //存储边
int k; //存储一共有多少条边
//--------------------------------------
vector<double> v; //用于离散化
bool cmp(edge a,edge b){
return a.x < b.x;
}
void pushup(int p){
//如果该结点的一整段[l,r]都需要算入的话,就直接查找离散化后的表v就行
if(tree[p].count)
tree[p].len = v[tree[p].r + 1] - v[tree[p].l];
//没有全部覆盖那么就是左右孩子的有效长度相加
else if(tree[p].count == 0 && tree[p].l != tree[p].r)
tree[p].len = tree[p<<1].len + tree[p<<1|1].len;
//该最小片段未被选中
else
tree[p].len = 0;
}
void build(int p,int l,int r){
tree[p] = {l,r,0,0};
if(l == r)
return;
int lc = p<<1,rc = p<<1|1,mid = l+r>>1;
build(lc,l,mid);
build(rc,mid + 1,r);
pushup(p);
}
void update(int p,int l,int r,int k){
//第一次遇到这条边,count+1有效,而如果是第二次遇到,那么久-1无效
if(tree[p].l == l && tree[p].r == r)
tree[p].count += k;
else{
int lc = p<<1,rc = p<<1|1,mid = tree[p].l+tree[p].r>>1;
if(r<=mid)
update(lc,l,r,k);
else if(l > mid)
update(rc,l,r,k);
else
update(lc,l,mid,k),update(rc,mid + 1,r,k);
}
pushup(p);
}
int find_w(double x){
return lower_bound(v.begin(),v.end(),x) - v.begin();
}
signed main(){
int T = 0;
while(cin>>n && n){
k = 0;
v.clear();
cout<<"Test case #"<<++T<<"\n";
double x1,y1,x2,y2;
double ans = 0;
for(int i = 1;i <= n;++i){
cin>>x1>>y1>>x2>>y2;
E[k].x = x1;E[k].y1 = y1,E[k].y2 = y2,E[k++].k = 1;
E[k].x = x2;E[k].y1 = y1,E[k].y2 = y2,E[k++].k = -1;
v.push_back(y1),v.push_back(y2); //存储一个矩形的两条边,并且为离散化做准备
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end()); //去重离散化
build(1,0,v.size() - 2); //建立线段树
sort(E,E + k,cmp); //将所有的线按照x从左到右进行排序,以便后续扫描
for(int i = 0;i < k;++i){
if(i > 0)
ans += tree[1].len * (E[i].x - E[i-1].x);
update(1,find_w(E[i].y1),find_w(E[i].y2) - 1,E[i].k);
}
// cout<<"Total explored area: "<<ans<<"\n\n";
printf("Total explored area: %.2lf\n\n",ans);
}
return 0;
}