之前一直在用线段树,但是一遇到扫描线的知识就扔给队友,距离最后一场比赛不到一周的时间了,把之前没学懂的东西补一补。
先来看一个例题
给定平面上的n个矩阵,不同矩阵之间可能有覆盖的部分,问你最后有矩阵覆盖的面积为多少。
包含多组,每组第一行包含一个数字n,接下来是n行,每行包含4个数字,分别为矩阵左下角的x,y坐标,右上角的x,y坐标
对于每组输入,包含一个数字,表示覆盖的面积
2
1 1 3 3
2 2 4 4
0
7
对于这个题目,我们考虑从左到右扫描这个奇怪的图形,我们将这个图形按照x坐标进行分段处理:
①x在[1,2]之间,此时y的范围是[1,3],这一部分的面积为2;
②x在[2,3]之间,此时y的范围是[1,4],这一部分的面积为3;
③x在[3,4]之间,此时y的范围是[2,4],这一部分的面积为2.
通过上边的计算步骤,我们不难发现以下几点:
①划分部分的标准是以矩形的左下角和右上角的x坐标,在两个相邻的划分点之间,y的范围是不变的。
②最后总的面积等于每一部分的x坐标的长度乘以对应的y坐标范围
③通过前两点我们不难发现只要能够动态的维护好每一段y的范围就可以算出准确答案。对于y的变化,我们注意到在矩形的左边界,会将这个矩形的y范围与原来的范围进行结合,比如原来的范围为[1,3],现在加入的范围为[2,4],那么最后的范围为[1,4]。同时对于右边界我们发现,它会使得y的范围减去一部分,当然如果有多个矩形包含当前的区间就不会改变或者部分改变。我们利用去重后y的坐标进行线段树的建树,那么可以得到线段树的点代表一段区间,同时我们维护一个lazy数组,lazy[i]表示的是i所代表的区间被多少个矩阵完全覆盖。当我们扫描到矩阵的右边界的时候,我们通过改变lazy数组的大小来维护y的范围。
我们结合样例来理解一下
首先我们将所有的y值加入Y数组并排序去重,同时将最大的y放到尾部,我们可以得到如下的Y数组:
Y[] = {0,1,2,3,4,4};
我们所建线段树如下
l[1] = 1;r[1] = 4;代表的区间为Y[1]到Y[5]
l[2] = 1;r[2] = 2;代表的区间为Y[1]到Y[3]
l[3] = 3;r[3] = 4;代表的区间为Y[3]到Y[5]
l[4] = 1;r[4] = 1;代表的区间为Y[1]到Y[2]
l[5] = 2;r[5] = 2;代表的区间为Y[2]到Y[3]
l[6] = 3;r[6] = 3;代表的区间为Y[3]到Y[4]
l[7] = 4;r[7] = 4;代表的区间为Y[4]到Y[5]
其中我们tree数组表示当前区间里被覆盖的长度
我们从左到右依次加入边,每条边有三个参数,y的左边界,y的右边界,矩阵的左边界还是右边界(1为左边界,-1为右边界)
初始状态
i | tree | lazy |
---|---|---|
1 | 0 | 0 |
2 | 0 | 0 |
3 | 0 | 0 |
4 | 0 | 0 |
5 | 0 | 0 |
6 | 0 | 0 |
7 | 0 | 0 |
加入边(1,3,1)
i | tree | lazy |
---|---|---|
1 | 2 | 0 |
2 | 2 | 1 |
3 | 0 | 0 |
4 | 0 | 0 |
5 | 0 | 0 |
6 | 0 | 0 |
7 | 0 | 0 |
其中我们没有下传,是因为我们查询的时候查询的是范围,在点2已经可以获取值,没有必要下传。
加入边(2,4,1)
i | tree | lazy |
---|---|---|
1 | 4 | 0 |
2 | 2 | 1 |
3 | 2 | 0 |
4 | 0 | 0 |
5 | 1 | 1 |
6 | 1 | 1 |
7 | 0 | 0 |
加入边(1,3,-1)
i | tree | lazy |
---|---|---|
1 | 3 | 0 |
2 | 1 | 0 |
3 | 2 | 0 |
4 | 0 | 0 |
5 | 1 | 1 |
6 | 1 | 1 |
7 | 0 | 0 |
此时,点2的lazy被消除,它所在的区间覆盖长度要去更细的划分中去找,也就是左儿子与右儿子。
最后一条边我们不用去加上,因为加入之后覆盖范围为零
加入边的代码如下:
void push_up(int id,int l,int r)
{
if(lazy[id])//如果当前区间被完全覆盖,那么长度就是左右端点的差值
tree[id] = Y[r + 1] - Y[l];
else if(l == r)
tree[id] = 0;
else//向左右儿子去找
{
tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
}
void add(int id,int l,int r,int L,int R,ll flag)
{
if(L <= l && r <= R)//当前节点的区间被完全覆盖
{
lazy[id] += flag;
push_up(id,l,r);
return ;
}
int mid = (l + r) >> 1;
if(L <= mid)
add(id << 1,l,mid,L,R,flag);
if(R > mid)
add(id << 1 | 1,mid + 1,r,L,R,flag);
push_up(id,l,r);
}
至此我们的核心部分已经完成了
完整代码如下:
/*************************************************************************
> File Name: HDU_1542.cpp
> Author:
> Mail:
> Created Time: 2019年12月09日 星期一 15时58分06秒
************************************************************************/
#include
#include
#define MAXN 100003
using namespace std;
struct Line
{
double x,y1,y2;
int flag;
void var(double _x,double _y1,double _y2,int _flag)
{
x = _x;
y1 = _y1;
y2 = _y2;
flag = _flag;
}
bool operator < (const Line &t) const
{
return x < t.x;
}
}line[MAXN << 1];
double Y[MAXN << 1],tree[MAXN << 3];
int lazy[MAXN << 3];
void build(int id,int l,int r)
{
tree[id] = 0;
lazy[id] = 0;
if(l == r)
return ;
int mid = (l + r) >> 1;
build(id << 1,l,mid);
build(id << 1 | 1,mid + 1,r);
}
void push_up(int id,int l,int r)
{
//当前区间被完全覆盖,则区间长度就是tree值
if(lazy[id])
tree[id] = Y[r + 1] - Y[l];
else if(l == r)
tree[id] = 0;
else//向左右子树更新答案
{
tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
}
void add(int id,int l,int r,int L,int R,int flag)
{
if(L <= l && r <= R)
{
lazy[id] += flag;
push_up(id,l,r);
return ;
}
int mid = (l + r) >> 1;
if(L <= mid)
add(id << 1,l,mid,L,R,flag);
if(R > mid)
add(id << 1 | 1,mid + 1,r,L,R,flag);
push_up(id,l,r);
}
int main()
{
int n;
int T = 0;
while(scanf("%d",&n) && n )
{
double x1,y1,x2,y2;
int cnt = 0,cnt1 = 0;
for(int i = 1;i <= n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
if(x1 == x2||y1 == y2)
continue;
//Y保存建树所需要的区间
Y[++cnt] = y1;
Y[++cnt] = y2;
//line表示在扫描过程中要加入和删去的线
line[++cnt1].var(min(x1,x2),min(y1,y2),max(y1,y2),1);
line[++cnt1].var(max(x1,x2),min(y1,y2),max(y1,y2),-1);
}
if(!cnt1)
{
cout << "0" << endl;
return 0;
}
sort(Y + 1,Y + 1 + cnt);
sort(line + 1,line + 1 + cnt1);
//Y去重
int tot = unique(Y + 1,Y + 1 + cnt) - (Y + 1);
Y[tot + 1] = Y[tot];
build(1,1,tot);
double ans = 0;
for(int i = 1;i < cnt1;i++)
{
//找到当前y在线段树中对应的位置,从而进行区间修改,因为点的范围为Y[l]到Y[r + 1],所以y2要减1
int y1 = upper_bound(Y + 1,Y + 1 + tot,line[i].y1) - Y - 1;
int y2 = upper_bound(Y + 1,Y + 1 + tot,line[i].y2) - Y - 1;
y2--;
add(1,1,tot,y1,y2,line[i].flag);
ans += (line[i + 1].x - line[i].x) * tree[1];
}
printf("Test case #%d\n",++T);
printf("Total explored area: %.2f\n\n",ans);
}
return 0;
}