之前一直在用线段树,但是一遇到扫描线的知识就扔给队友,距离最后一场比赛不到一周的时间了,把之前没学懂的东西补一补。
先来看一个例题
例题:HDU1542
给定平面上的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];
}