暂无链接
题目描述
在2051年,若干火星探险队探索了这颗红色行星的不同区域并且制作了这些区域的地图。现在,Baltic空间机构有一个雄心勃勃的计划,他们想制作一张整个行星的地图。为了考虑必要的工作,他们需要知道地图上已经存在的全部区域的大小。你的任务是写一个计算这个区域大小的程序。
具体任务要求为:
(1)从输入中读取地图形状的描述;
(2)计算地图覆盖的全部的区域;
(3)输出探索区域的总面积(即所有矩形的公共面积)。
输入格式
输入的第一行包含一个整数n
(1≤n≤10000)
(
1
≤
n
≤
10000
)
,表示可得到的地图数目。
以下n行,每行描述一张地图。每行包含4个整数x1,y1,x2和y2(
0≤x1<x2≤30000
0
≤
x
1
<
x
2
≤
30000
,
0≤y1<y2≤30000
0
≤
y
1
<
y
2
≤
30000
)。数值
(x1,y1)
(
x
1
,
y
1
)
和
(x2,y2)
(
x
2
,
y
2
)
是坐标,分别表示绘制区域的左下角和右上角坐标。每张地图是矩形的,并且它的边是平行于x坐标轴或y坐标轴的。
输出格式
输出文件包含一个整数,表示探索区域的总面积(即所有矩形的公共面积)。
输入样例
2
10 10 20 20
15 15 25 30
输出样例
225
解题分析:
赤裸裸的扫描线…和 窗口的星星 的思路一样, 将x坐标离散化, 将上下底按y轴坐标排序, 用线段树维护当前存在的矩形下底。
我们可以将下底权值设为1,上底权值设为-1, 每次更新线段树区间后将面积加上当前可用的下底乘上与下一条底边间高度的差。
关键在于如何维护底的长度。 我们可以在线段树中维护一个值
len
l
e
n
表示当前区间可用的总长度, 以及另一个值
valid
v
a
l
i
d
表示当前区间是否可用。我们每次更新(加入一条新的边)时就更新根据valid值更新。有三种情况:
1.
valid!=0
v
a
l
i
d
!
=
0
:说明当前区间可用, 所以当前区域的可用底边长度即为
xright−xleft−1
x
r
i
g
h
t
−
x
l
e
f
t
−
1
2.
valid=0
v
a
l
i
d
=
0
且
lef=rig
l
e
f
=
r
i
g
:说明当前点已没有线段覆盖,可用长度为0。
3.其他情况:即
lef!=rig
l
e
f
!
=
r
i
g
且
valid=0
v
a
l
i
d
=
0
, 说明之前可能有modify到更小子区域的情况,(在这里valid并不是向上传导的), 所以当前区域的可用底边长度为
len(sonleft)+len(sonright)
l
e
n
(
s
o
n
l
e
f
t
)
+
l
e
n
(
s
o
n
r
i
g
h
t
)
。这个性质的前提是:因为每条下底都有对应的上底, 所以当上底加入时区间中还有可能有可用的区间底边,且不会与抵消了的下底重复, 因为加入下底的时候并没有将
len
l
e
n
下传到下一层的点, 下一层的len只可能是其他边赋上的。
下面上代码:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define R register
#define gc getchar()
#define W while
#define IN inline
#define MX 50005
#define db double
template <typename T>
IN void in(T &x)
{
x = 0; R char c = gc;
W (!isdigit(c)) c = gc;
W (isdigit(c))
{x = (x << 1) + (x << 3), x += c - 48, c = gc;}
}
namespace SGT
{
#define ls now << 1
#define rs now << 1 | 1
int x[MX], left[MX], right[MX];
int num, tot, cnt;
struct Edge
{
int x, y, id, typ;
}data[MX << 1], anti[MX << 1];
IN bool cmp_x (const Edge &x, const Edge &y)
{return x.x < y.x;}
IN bool cmp_y (const Edge &x, const Edge &y)
{return x.y == y.y ? x.typ > y.typ : x.y < y.y;}
//在这里和窗口的星星一题不同的是, 其实不必将负值的边排在前面,因为有多条边的时候它们之间的高为0
IN bool operator == (const Edge &x, const Edge &y)
{
return x.x == y.x;
}
struct Node
{
int valid, lef, rig, len;
}tree[MX << 1];
void build(int now, int l, int r)
{
tree[now].lef = l, tree[now].rig = r;
if(l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
IN void pushup(const int &now)
{
if(tree[now].valid) tree[now].len = x[tree[now].rig ] - x[tree[now].lef - 1];
//因为保存的为点, 为了不少加区间之间的那部分,左端点需要减一
else if(tree[now].lef == tree[now].rig) tree[now].len = 0;
else tree[now].len = tree[ls].len + tree[rs].len;
}
void modify(const int &now, const int &lb, const int &rb, const int &delta)
{
if(tree[now].lef == lb && tree[now].rig == rb)
{
tree[now].valid += delta;
pushup(now);
return;
}
int mid = (tree[now].lef + tree[now].rig) >> 1;
if(mid >= rb) modify(ls, lb, rb, delta);
else if(mid < lb) modify(rs, lb, rb, delta);
else
{
modify(ls, lb, mid, delta);
modify(rs, mid + 1, rb, delta);
}
pushup(now);
}
}
using namespace SGT;
using std::sort;
using std::unique;
int main()
{
int lf, dn, rg, up;
in(num);
for (R int i = 1; i <= num; ++i)
{
in(lf), in(dn), in(rg), in(up);
data[++tot].x = lf, data[tot].id = i, data[tot].typ = 0;
anti[tot].y = dn, anti[tot].id = i, anti[tot].typ = 0;
data[++tot].x = rg, data[tot].id = i, data[tot].typ = 1;
anti[tot].y = up, anti[tot].id = i, anti[tot].typ = 1;
}
sort(data + 1, data + 1 + tot, cmp_x);
sort(anti + 1, anti + 1 + tot, cmp_y);
for (R int i = 1; i <= tot; ++i)
{//离散化
if(i == 1 || data[i].x != data[i - 1].x) ++cnt;
x[cnt] = data[i].x;
if(!data[i].typ) left[data[i].id] = cnt;
else right[data[i].id] = cnt;
}
build(1, 1, cnt);
int ans = 0;
for (R int i = 1; i < tot; ++i)
{
if(!anti[i].typ)
modify(1, left[anti[i].id] + 1, right[anti[i].id], 1);
else//因为modify的时候lef减了一,所以这里要加上
modify(1, left[anti[i].id] + 1, right[anti[i].id], -1);
ans += (anti[i + 1].y - anti[i].y) * tree[1].len;
}
printf("%d", ans);
return 0;
}