题目大意:
希腊有几张亚特兰蒂斯的手绘地图,但是每张地图只记录了亚特兰蒂斯的部分区域(具体说每张地图只记录了一片长方形区域),现要求出所有这些区域面积的总和(公共部分只算一次)。
现有多个测例,每个测例中给出地图数量n(1 ≤ n ≤ 100),输入以n = 0结束,接下来会给出每张地图中描述的长方形的左上角坐标和右下角坐标(x轴朝右,y轴朝下,并且所有坐标用浮点数(double)表示,范围为[0, 100000]),对于每个测例求总面积大小。
注释代码:
/*
* Problem ID : POJ 1151 Atlantis
* Author : Lirx.t.Una
* Language : C++
* Run Time : 0 ms
* Run Memory : 172 KB
*/
#include <algorithm>
#include <iostream>
#include <cstdio>
//每条扫描线(即矩形的左右两边)
//是矩形的左边还是右边
//用于计数
//比如y区间中[3, 5]刚好有一条矩形的左边
//则此区域内的的有效值就+L(即+1),表示存在一条左边
//如果扫描到该矩形的右边,则此区域就+R(即-1),表示
//在该区域中抵消掉了一条边
#define L 1
#define R -1
//maximum nubmer of scanning lines
//扫描线的最大数量,为矩形数量的两倍
//每个矩形都有左右两条边
#define MAXSCN 201
//对所有矩形的上下边的y坐标离散化、唯一化后的
//y值的个数,最多为矩形数量的两倍
//每个矩形都有上下两条边
#define MAXYN 201
//maximum size of segment tree
//线段树的最大结点数(测试过的最优值)
#define MAXSGSIZE 530
//求t的左子树
#define LFT(t) ( t << 1 )
//求t的右子树
#define RHT(t) ( LFT(t) | 1 )
//都采用位操作能提高速度
using namespace std;
struct Scan {//扫描线,是纵向的
double x;//扫描线的x坐标
double l, h;//该扫描线的y的区间[low, hight]
char vv;//virtual value,该扫描线的有效值,可以为L或R
Scan(void) {}
Scan( double xx, double ll, double hh, char vv_ ) :
x(xx), l(ll), h(hh), vv(vv_) {}
bool//对扫描线按照x递增排序,扫描时就按照这个顺序扫描
operator<(const Scan &oth)
const {
return x < oth.x;
}
};
//将离散化后的y映射到线段树上
//映射的值为y数组的下标
struct Node {//线段树结点
short lft, rht;//lft、rht为离散y的下标映射
//改线段的有效值,即线段的厚度
//比如改线段中有n个矩形的左边重合,则vv为n,表示厚度
short vv;
double len;//改线段的有效长度,被多少长有效的边所覆盖
};
Scan scn[MAXSCN];//扫描线
Node seg[MAXSGSIZE];//线段树
double y[MAXYN];//对矩形y坐标离散外、唯一化(可以减小线段树的规模)
void
build( int tree, int lft, int rht ) {//构造先单数
seg[tree].lft = lft;
seg[tree].rht = rht;
seg[tree].vv = 0;
seg[tree].len = 0.0;
if ( 1 == rht - lft )//这里构建一个叶子结点长度至少为1,不能为0
return ;
int mid;
mid = ( lft + rht ) >> 1;
build( LFT(tree), lft, mid );
build( RHT(tree), mid, rht );
}
void
len_update(int tree) {//跟新当前结点的有效长度
int lft, rht;
lft = seg[tree].lft;
rht = seg[tree].rht;
if ( seg[tree].vv )//有效长度大于0直接取映射的实际长度
seg[tree].len = y[rht] - y[lft];
//为0但不代表其子树的有效长度为0,因为父结点的有效长度会传递给其子结点
else if ( 1 == rht - lft )//如果已经到了叶子结点就不用再向下探索了
seg[tree].len = 0;
else//否则就归纳(自底向上)求当前结点的有效长度
seg[tree].len = seg[ LFT(tree) ].len + seg[ RHT(tree) ].len;
}
void
update( int tree, Scan &s ) {//对线段树进行跟新
//最主要是利用扫描线跟新线段树的有效长度以及有效值
int lft, rht;
lft = seg[tree].lft;
rht = seg[tree].rht;
if ( y[lft] == s.l && y[rht] == s.h ) {//区间刚好对位
seg[tree].vv += s.vv;//对有效值进行累加
len_update(tree);//由于线段厚度改变因此需要重新计算其有效长度
return ;
}
int mid;
//以下为线段树的常规更新方法
mid = ( lft + rht ) >> 1;
if ( s.h <= y[mid] )
update( LFT(tree), s );
else if ( s.l >= y[mid] )
update( RHT(tree), s );
else {
//中间出现断点
//用y[mid]接上
Scan ss(s);
ss.h = y[mid];
update( LFT(tree), ss );
ss = s;
ss.l = y[mid];
update( RHT(tree), ss );
}
len_update(tree);//由于其子树更新过了,因此会影响到整个线段树的有效长度
//因此需要重新计算其有效长度
}
int
main() {
int t;//测例数
int n;//矩形数量
int ns;//number of scanning lines,扫描线的数量
int ny;//number of discrete y,离散化y的个数
int i;//计数变量
double ans;//最后覆盖的总面积
//用于接收输入的坐标
double x1, y1;
double x2, y2;
t = 0;
while ( scanf("%d", &n), n ) {
ns = 0;
ny = 0;
while ( n-- ) {
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
scn[ns++] = Scan( x1, y1, y2, L );
scn[ns++] = Scan( x2, y1, y2, R );
y[ny++] = y1;
y[ny++] = y2;
}
sort(scn, scn + ns);//扫描线按x递增排序
sort(y, y + ny);//y按照y递增排序,即离散化
ny = unique(y, y + ny) - y;//STL泛型函数,将容器里的元素唯一化
//前提是容器必须现排序,返回唯一化后新数组的end迭代器,因此长度缩短
//需要通过 - y得到新容器的大小
build( 1, 0, ny - 1 );
for ( ans = 0.0, i = 0; i < ns; i++ ) {
update( 1, scn[i] );//对整个y区间跟新,得到一个跟新后的有效长度
ans += seg[1].len * ( scn[i + 1].x - scn[i].x );//逐个扫描并累加面积
//长有有效值决定,宽为最近两个x相减而得,一直往后递推下去
}
printf("Test case #%d\n", ++t);
printf("Total explored area: %.2lf\n\n", ans);
}
return 0;
}
无注释代码:
#include <algorithm>
#include <iostream>
#include <cstdio>
#define L 1
#define R -1
#define MAXSCN 201
#define MAXYN 201
#define MAXSGSIZE 530
#define LFT(t) ( t << 1 )
#define RHT(t) ( LFT(t) | 1 )
using namespace std;
struct Scan {
double x;
double l, h;
char vv;
Scan(void) {}
Scan( double xx, double ll, double hh, char vv_ ) :
x(xx), l(ll), h(hh), vv(vv_) {}
bool
operator<(const Scan &oth)
const {
return x < oth.x;
}
};
struct Node {
short lft, rht;
short vv;
double len;
};
Scan scn[MAXSCN];
Node seg[MAXSGSIZE];
double y[MAXYN];
void
build( int tree, int lft, int rht ) {
seg[tree].lft = lft;
seg[tree].rht = rht;
seg[tree].vv = 0;
seg[tree].len = 0.0;
if ( 1 == rht - lft )
return ;
int mid;
mid = ( lft + rht ) >> 1;
build( LFT(tree), lft, mid );
build( RHT(tree), mid, rht );
}
void
len_update(int tree) {
int lft, rht;
lft = seg[tree].lft;
rht = seg[tree].rht;
if ( seg[tree].vv )
seg[tree].len = y[rht] - y[lft];
else if ( 1 == rht - lft )
seg[tree].len = 0;
else
seg[tree].len = seg[ LFT(tree) ].len + seg[ RHT(tree) ].len;
}
void
update( int tree, Scan &s ) {
int lft, rht;
lft = seg[tree].lft;
rht = seg[tree].rht;
if ( y[lft] == s.l && y[rht] == s.h ) {
seg[tree].vv += s.vv;
len_update(tree);
return ;
}
int mid;
mid = ( lft + rht ) >> 1;
if ( s.h <= y[mid] )
update( LFT(tree), s );
else if ( s.l >= y[mid] )
update( RHT(tree), s );
else {
Scan ss(s);
ss.h = y[mid];
update( LFT(tree), ss );
ss = s;
ss.l = y[mid];
update( RHT(tree), ss );
}
len_update(tree);
}
int
main() {
int t;
int n;
int ns;
int ny;
int i;
double ans;
double x1, y1;
double x2, y2;
t = 0;
while ( scanf("%d", &n), n ) {
ns = 0;
ny = 0;
while ( n-- ) {
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
scn[ns++] = Scan( x1, y1, y2, L );
scn[ns++] = Scan( x2, y1, y2, R );
y[ny++] = y1;
y[ny++] = y2;
}
sort(scn, scn + ns);
sort(y, y + ny);
ny = unique(y, y + ny) - y;
build( 1, 0, ny - 1 );
for ( ans = 0.0, i = 0; i < ns; i++ ) {
update( 1, scn[i] );
ans += seg[1].len * ( scn[i + 1].x - scn[i].x );
}
printf("Test case #%d\n", ++t);
printf("Total explored area: %.2lf\n\n", ans);
}
return 0;
}
单词解释:
Greek:n, 希腊人,希伯来语; adj, 希腊人的
fable:n, 寓言
fabled:adj, 寓言中的
Atlantis:地名,亚特兰蒂斯(传说中沉没于大西洋的岛屿)
unwise:adj, 不明智的
top-left:左上角
bottom-right:右下角