欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【详解】线段树扫描线

程序员文章站 2022-07-13 11:18:50
...

题目

「VOJ1056」图形面积

桌面上放了NN个平行于坐标轴的矩形,这NN个矩形可能有互相覆盖的部分,求它们组成的图形的面积。
【输入】
有多组测试数据。
每组测试数据输入第一行为一个数NN,表示矩形的数量。
下面NN行,每行四个整数,分别表示每个矩形的左下角和右上角的坐标,坐标范围为108–10^810810^8之间的整数。
N==0N==0时,测试文件结束。
【输出】
对于每个测试用例,在单独一行中输出一个整数,表示图形的面积。
【样例输入】

1
1 2 3 4
3
1 1 4 3
2 -1 3 2
4 0 5 2
0

【样例输出】

4
10

【数据范围】
对于50%50\%的数据,0<N<=1000<N<=100,每个测试点测试数据组数<=20<=20
对于50%50\%的数据,坐标范围为500–500500500之间的整数。
对于100%100\%的数据,0<N<=20000<N<=2000,每个测试点测试数据组数<=50<=50,坐标范围为108–10^810810^8之间的整数。

线段树扫描线

线段树扫描线就是用来解决图像面积并的问题。
例如存在下面三个矩形:
【详解】线段树扫描线
我们可以根据矩形的左右边界分成多个部分求解:
【详解】线段树扫描线
【详解】线段树扫描线
这些红色的线就称为扫描线

扫描线

从左往右扫过矩形左右边,每两条扫描线之间的面积总和就是图形面积。
每一段面积 = (覆盖扫描线上的长度和)*(与下一条扫描线的距离)。
可以发现,影响扫描线上的覆盖长度,只与矩形左右边相关,因此可以将图形简化:
【详解】线段树扫描线
构造一个四元组line(x,y,yy,flag)line(x,y,yy,flag),即结构体,来存在这些矩形左右边。
xx:边的横坐标;
y,yyy,yy:边的两个端点,长度为yy-y;
flagflag11表示矩形的左边界,1-1表示矩形的右边界。

在扫描线上的覆盖长度,可以看作边在yy轴上的投影,我们将投影分成多段区间:
【详解】线段树扫描线
每一条边的投影包含连续的多段区间,求扫描线的覆盖总长度,其实就是多段区间的覆盖长度。
如果将每段区间看作序列中的一个点,就相当于线段树的区间修改、区间求和。
(1)构造一个线段树维护的序列,每个点对应一个区间。
因为纵坐标yy有重复,需要去重排序。设去重排序后的序列为dy[]dy[],有tt个。分成t1t-1段区间。
那么第ii段区间的左端点为dy[i]dy[i],区间长度为dy[i+1]dy[i]dy[i+1]-dy[i]
【详解】线段树扫描线
再添加两个数组:
cnt[k]cnt[k]:表示线段树结点kk表示区间的覆盖次数。
len[k]len[k]:表示线段树结点kk表示覆盖区间的长度。
(2)对于每一条边line(x,y,yy,flag)line(x,y,yy,flag),相当于对覆盖的区间结点进行增加flagflag
如何找到yyyyyy对应哪一段区间?
二分查找,因为dy[]dy[]已经是去重且有序的。
需要注意边界情况:
yy=Half()1yy=Half()-1yy>yyy>y,表示第Half()1Half()-1段区间,区间为[dy[ef()1],dy[ef()])[ dy[ef()-1] , dy[ef()] )Half()Half()为二分函数。
同时根据cnt[k]cnt[k]覆盖次数更新结点:
当覆盖次数为00时,结点代表的区间不参与计算。

	if(cnt[k]!=0) len[k]=dy[r+1]-dy[l];			//进行了覆盖,区间长度就是第l段~第r段区间的长度
	else len[k]=len[2*k]+len[2*k+1];			//没有覆盖,区间长度就是左右孩子区间长度之和

(3)区间求和,求投影到yy轴的覆盖区间。
对于两条边line1(x1,y1,yy1,flag1)line2(x2,y2,yy2,flag2)line_1(x_1,y_1,yy_1,flag_1),line_2(x_2,y_2,yy_2,flag_2)
扫描线之间的宽度就是x2x1x_2-x_1
扫描线上的覆盖长度是len[1]len[1]
两条扫描线之间的面积:
len(x2x1)len*(x2-x1)
ans+=len(x2x1)ans+=len*(x2-x1)ansans就是最后的答案。

参考程序

#include<bits/stdc++.h>
#define N 2010
#define LL long long
using namespace std;
struct Line
{
    int x,y,yy,flag;    //扫面线,横坐标为x,长度为y~yy 
}line[2*N];
struct Tree
{
    LL len;
    int cnt;
}tree[10*N];//cnt统计区间覆盖次数,len统计覆盖的区间长度
int n;
int temp[2*N],dy[2*N],t;     
void dist()             //纵坐标去重排序
{
    sort(temp+1,temp+n+1);
    dy[++t]=temp[1]; 
    for(int i=2;i<=n;i++)
        if(temp[i]!=temp[i-1]) dy[++t]=temp[i];
    t--;        //有t个点,分成t-1个段区间 
    return;
} 
int comp(Line a,Line b)
{
    return a.x<b.x?1:0;
}
void built(int k,int l,int r)   //建树,初始化,第k个结点表示区间[dy[l],dy[l+1]] 
{
    tree[k].len=0;               
    tree[k].cnt=0;
    if(l==r)    return;
    int mid=(l+r)/2;
    built(2*k,l,mid);
    built(2*k+1,mid+1,r);
}
int Half(int k)         //二分查找纵坐标对应那一段区间 
{
    int l=1,r=t+1;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(dy[mid]==k) return mid;
        if(dy[mid]<k) l=mid+1;
        else r=mid-1; 
    }
}
void pushdown(int k,int l,int r)
{
    if(tree[k].cnt>0) tree[k].len=dy[r+1]-dy[l]; //覆盖的区间长度 
    else tree[k].len=tree[2*k].len+tree[2*k+1].len;
}
void update(int k,int l,int r,int X,int Y,int T)
{
    if(l>Y||r<X) return;  //第l~r段区间,是[dy[l],dy[r+1])
    if(X<=l&&r<=Y)
    {
        tree[k].cnt+=T;
        pushdown(k,l,r);
        return;
    }
    int mid=(l+r)/2;
    update(2*k,l,mid,X,Y,T);
    update(2*k+1,mid+1,r,X,Y,T);
    pushdown(k,l,r);                //回溯更新len 
}
int main()
{
    while((scanf("%d",&n))&&n)
    {
        LL ans=0;
        t=0;
        int a,b,c,d;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d%d",&a,&b,&c,&d);
            line[2*i-1]=Line{a,b,d,1};      //矩形左边界 
            line[2*i]=Line{c,b,d,-1};       //矩形右边界 
            temp[2*i-1]=b;          //纵坐标 
            temp[2*i]=d;
        }
        n*=2;
        dist(); //纵坐标去重排序  
        built(1,1,t);
        sort(line+1,line+n+1,comp);     //扫描线排序
        for(int i=1;i<=n;i++)
        {
            if(i>1&&line[i].x!=line[i-1].x)  ans+=(line[i].x-line[i-1].x)*tree[1].len;
            int X=Half(line[i].y),Y=Half(line[i].yy)-1;      
            update(1,1,t,X,Y,line[i].flag);
        }
        printf("%lld\n",ans);
    } 
    return 0;
 } 
相关标签: 数据结构