扫描线主要用于解决矩形面积交,面积并,周长等问题,且通常与线段树、离散化连用
矩形面积并
这里借用 leetcode850. 矩形面积 II 进行讲解。
题意
解题思路
可以看到,坐标的范围很大,采用暴力解法肯定是不行的,这时就要引入扫描线算法了。
下面借用 OI Wiki 扫描线的一张图
扫描线就是一根线,从下往上扫(当然也可以从左往右扫),我们以每个矩形的上下两条边为界,可以把整个矩形组成的多边形分成如图各个颜色不同的小矩形,那么这个小矩形的高就是我们扫过的距离,那么剩下了一个变量,那就是矩形的长一直在变化。
那么这个长是怎么变化的呢?我们可以看到矩形的长每次都是扫过的矩形下边的并,而当扫到一个矩形的上边时,就把该矩形的下边从这个并集里去掉。那么就可以通过线段树维护这个下边的并集。
线段树结构如下:
struct node
{
int l/*区间左边界*/,r/*区间右边界*/,lazy/*懒惰标记*/,sum/*区间元素之和*/;
}tree[2050];
这里的 l
和 r
分别表示这个节点的边的范围为 从点 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);
}
};