扫描线入门

扫描线主要用于解决矩形面积交,面积并,周长等问题,且通常与线段树、离散化连用



矩形面积并

这里借用 leetcode850. 矩形面积 II 进行讲解。

题意


解题思路

可以看到,坐标的范围很大,采用暴力解法肯定是不行的,这时就要引入扫描线算法了。

下面借用 OI Wiki 扫描线的一张图

扫描线就是一根线,从下往上扫(当然也可以从左往右扫),我们以每个矩形的上下两条边为界,可以把整个矩形组成的多边形分成如图各个颜色不同的小矩形,那么这个小矩形的高就是我们扫过的距离,那么剩下了一个变量,那就是矩形的长一直在变化。

那么这个长是怎么变化的呢?我们可以看到矩形的长每次都是扫过的矩形下边的并,而当扫到一个矩形的上边时,就把该矩形的下边从这个并集里去掉。那么就可以通过线段树维护这个下边的并集。

线段树结构如下:

struct node
{
    int l/*区间左边界*/,r/*区间右边界*/,lazy/*懒惰标记*/,sum/*区间元素之和*/;
}tree[2050];

这里的 lr 分别表示这个节点的边的范围为 从点 l-1 到 点r 这条边,这里说明一下为什么可行。

lazy 表示这个区间是否被整个操作过,我们用 1 表示一个矩形的下边界,-1 表示这个矩形的上边界。那么扫描线扫到下边界时这个区间对应 lazy +1,扫到上边界时这个区间对应 lazy -1。sum 表示这个区间实际覆盖长度。因为有可能两个区间并没有连起来,如下图:

  • 初始化线段树
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	if(l==r)//递归到叶节点
		return;
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}

具体说一下 update ,如果当前区间 lazy>0,说明这个区间被完全覆盖了,否则其等于两个子区间覆盖之和。

void update(int k) // 更新节点k的sum;
{
    if(tree[k].lazy > 0)
        tree[k].sum=a[tree[k].r]-a[tree[k].l-1];
    else
        tree[k].sum=tree[k<<1].sum + tree[k<<1|1].sum;
}
  • 区间更新,这里说一下为什么 lazy 不用下传,是因为之前的区间查询每次查的区间不一样,可能会查到某个区间的子区间,所以 lazy 需要下传到子区间,而这里每次只需要查一个总的区间 tree[1].sum,所以不需要下传。
void add(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, int val/*区间每个元素增加的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].lazy += val;
        if(tree[k].lazy > 0)
        {
            //cout<<tree[k].r-1<<" "<<a[tree[k].r-1]<<endl;
            tree[k].sum = a[tree[k].r]-a[tree[k].l-1];
        }
        else
            tree[k].sum = tree[k<<1].sum + tree[k<<1|1].sum;
        return ;
    }
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        add(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        add(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}
  • 主程序,需要因为坐标很大,所以需要离散化(就是把所有长度从小到大排序,然后去重,需要对应长度的位置,就使用二分法找到(这里使用了 lower_bound))
int rectangleArea(vector<vector<int>>& rectangles) {
            int n = rectangles.size();
            for(int i=0; i<n; ++i)
            {
                Rects[i].x1 = rectangles[i][0];
                Rects[i].h = rectangles[i][1];
                Rects[i].x2 = rectangles[i][2];
                Rects[i].flag = 1;
                Rects[i+n].x1 = rectangles[i][0];
                Rects[i+n].h = rectangles[i][3];
                Rects[i+n].x2 = rectangles[i][2];
                Rects[i+n].flag = -1;
                a.push_back(rectangles[i][0]);
                a.push_back(rectangles[i][2]);
            }
            sort(a.begin(), a.end());
            int size = unique(a.begin(), a.end()) - a.begin();
            // cout<<size<<endl;
            sort(Rects, Rects+2*n, [](rect r1, rect r2){
                if(r1.h == r2.h)
                {
                    return r1.flag > r2.flag;
                }
                return r1.h < r2.h;
            });
            long long ans = 0;
            build(1,1,size-1);
            add(1, lower_bound(a.begin(),a.begin()+size,Rects[0].x1)-a.begin()+1, lower_bound(a.begin(),a.begin()+size,Rects[0].x2)-a.begin(), Rects[0].flag);
            // cout<<Rects[0].x2<<" "<<lower_bound(a.begin(),a.begin()+size,Rects[0].x2)-a.begin()+1<<endl;
            for(int i=1; i<2*n; ++i)
            {
                cout<<tree[1].sum<<endl;
                ans += (long long)(Rects[i].h-Rects[i-1].h)*tree[1].sum;
                add(1, lower_bound(a.begin(),a.begin()+size,Rects[i].x1)-a.begin()+1, lower_bound(a.begin(),a.begin()+size,Rects[i].x2)-a.begin(), Rects[i].flag);
                // cout<<tree[1].sum<<" "<<ans<<endl;
                // cout<<Rects[i].h<<endl;
            }
            return (int)(ans%mod);
    }

代码(C++)

const long long mod = 1e9+7;
class Solution {
public:
    vector<int> a;
    struct rect
    {
        int x1,x2,h,flag;
    } Rects[405];
    struct node
    {
        int l/*区间左边界*/,r/*区间右边界*/,lazy/*懒惰标记*/,sum/*区间元素之和*/;
    }tree[2050];
void update(int k) // 更新节点k的sum;
{
    if(tree[k].lazy > 0)
        tree[k].sum=a[tree[k].r]-a[tree[k].l-1];
    else
        tree[k].sum=tree[k<<1].sum + tree[k<<1|1].sum;
}
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/) //初始化线段树
{
	tree[k].l=l,tree[k].r=r;
	if(l==r)//递归到叶节点
		return;
	int mid=(l+r)>>1;//计算左右子节点的边界
	build(k<<1,l,mid);//递归进左儿子[l,mid]
	build(k<<1|1,mid+1,r);//递归进右儿子[mid+1,r]
	update(k);//最后要用左右子区间的值更新该区间的值
}
void add(int k/*当前节点的编号*/, int l/*当前更新区间的左边界*/, int r/*当前更新区间的右边界*/, int val/*区间每个元素增加的值*/) // 区间更新
{
    //如果当前区间是更新区间的子区间,更新sum与lazy
    if(tree[k].l>=l&&tree[k].r<=r)
    {
        tree[k].lazy += val;
        if(tree[k].lazy > 0)
        {
            //cout<<tree[k].r-1<<" "<<a[tree[k].r-1]<<endl;
            tree[k].sum = a[tree[k].r]-a[tree[k].l-1];
        }
        else
            tree[k].sum = tree[k<<1].sum + tree[k<<1|1].sum;
        return ;
    }
    int mid=(tree[k].l+tree[k].r)>>1;//计算下一层子区间的左右边界
    //如果更新区间包含左子区间的部分
    if(l<=mid)
        add(k<<1,l,r,val);
    if(r>mid) //如果更新区间包含右子区间的部分
        add(k<<1|1,l,r,val);
    update(k);//最后更新点k的值
}
int rectangleArea(vector<vector<int>>& rectangles) {
            int n = rectangles.size();
            for(int i=0; i<n; ++i)
            {
                Rects[i].x1 = rectangles[i][0];
                Rects[i].h = rectangles[i][1];
                Rects[i].x2 = rectangles[i][2];
                Rects[i].flag = 1;
                Rects[i+n].x1 = rectangles[i][0];
                Rects[i+n].h = rectangles[i][3];
                Rects[i+n].x2 = rectangles[i][2];
                Rects[i+n].flag = -1;
                a.push_back(rectangles[i][0]);
                a.push_back(rectangles[i][2]);
            }
            sort(a.begin(), a.end());
            int size = unique(a.begin(), a.end()) - a.begin();
            // cout<<size<<endl;
            sort(Rects, Rects+2*n, [](rect r1, rect r2){
                if(r1.h == r2.h)
                {
                    return r1.flag > r2.flag;
                }
                return r1.h < r2.h;
            });
            long long ans = 0;
            build(1,1,size-1);
            add(1, lower_bound(a.begin(),a.begin()+size,Rects[0].x1)-a.begin()+1, lower_bound(a.begin(),a.begin()+size,Rects[0].x2)-a.begin(), Rects[0].flag);
            // cout<<Rects[0].x2<<" "<<lower_bound(a.begin(),a.begin()+size,Rects[0].x2)-a.begin()+1<<endl;
            for(int i=1; i<2*n; ++i)
            {
                cout<<tree[1].sum<<endl;
                ans += (long long)(Rects[i].h-Rects[i-1].h)*tree[1].sum;
                add(1, lower_bound(a.begin(),a.begin()+size,Rects[i].x1)-a.begin()+1, lower_bound(a.begin(),a.begin()+size,Rects[i].x2)-a.begin(), Rects[i].flag);
                // cout<<tree[1].sum<<" "<<ans<<endl;
                // cout<<Rects[i].h<<endl;
            }
            return (int)(ans%mod);
    }
};
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值