扫描线入门&HDU_1542

之前一直在用线段树,但是一遇到扫描线的知识就扔给队友,距离最后一场比赛不到一周的时间了,把之前没学懂的东西补一补。
先来看一个例题

例题:HDU1542

给定平面上的n个矩阵,不同矩阵之间可能有覆盖的部分,问你最后有矩阵覆盖的面积为多少。

输入

包含多组,每组第一行包含一个数字n,接下来是n行,每行包含4个数字,分别为矩阵左下角的x,y坐标,右上角的x,y坐标

输出

对于每组输入,包含一个数字,表示覆盖的面积

输入样例

2
1 1 3 3
2 2 4 4
0

输出样例

7

对于这个题目,我们考虑从左到右扫描这个奇怪的图形,我们将这个图形按照x坐标进行分段处理:
扫描线入门&HDU_1542_第1张图片
①x在[1,2]之间,此时y的范围是[1,3],这一部分的面积为2;
扫描线入门&HDU_1542_第2张图片
②x在[2,3]之间,此时y的范围是[1,4],这一部分的面积为3;
扫描线入门&HDU_1542_第3张图片
③x在[3,4]之间,此时y的范围是[2,4],这一部分的面积为2.
扫描线入门&HDU_1542_第4张图片
通过上边的计算步骤,我们不难发现以下几点:
①划分部分的标准是以矩形的左下角和右上角的x坐标,在两个相邻的划分点之间,y的范围是不变的。
②最后总的面积等于每一部分的x坐标的长度乘以对应的y坐标范围
③通过前两点我们不难发现只要能够动态的维护好每一段y的范围就可以算出准确答案。对于y的变化,我们注意到在矩形的左边界,会将这个矩形的y范围与原来的范围进行结合,比如原来的范围为[1,3],现在加入的范围为[2,4],那么最后的范围为[1,4]。同时对于右边界我们发现,它会使得y的范围减去一部分,当然如果有多个矩形包含当前的区间就不会改变或者部分改变。我们利用去重后y的坐标进行线段树的建树,那么可以得到线段树的点代表一段区间,同时我们维护一个lazy数组,lazy[i]表示的是i所代表的区间被多少个矩阵完全覆盖。当我们扫描到矩阵的右边界的时候,我们通过改变lazy数组的大小来维护y的范围。
我们结合样例来理解一下
扫描线入门&HDU_1542_第5张图片
首先我们将所有的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;
}

你可能感兴趣的:(数据结构)