扫描线总结

引入

面积并(周长并)
如下图给你一堆矩形求它的面积并或周长并。
image
显然直接做,就是考虑容斥,但明显不好做。
那就思考如何切割或补,显然补完要减的图形也不规整,只能考虑割。
如何将其割成规整的图形,明显矩形最容易计算和割。
把它割成矩形后发现,每次遇到某个矩形的边就会变,所以考虑一条线从下向上扫,每遇到一个与扫描线平行的边就计算一次答案。
面积并答案为线上被覆盖的长度乘与上一条边的距离。
周长并答案分俩个部分,平行的就是线上被覆盖的长度,垂直的就是线上有多少个端点(在线段内的不算)乘与上一条边的距离。
如何快速维护线上的覆盖长度和有多少段点,显然一个区间修改问题,用线段树。

代码实现

周长并

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int n,ans,cnt,mx=-100010,mi=100010,las=0;
struct node
{
    int h,x,y,w;
}bi[N*2];
bool cmp(node x,node y)
{
    if(x.h==y.h)
        return x.w>y.w;//先加后减,不让会错
/*
2
0 0 4 4
0 4 4 8
*/

    return x.h<y.h;
}
struct stu
{
    int sum;//整个区间被整体覆盖了几次(类似lazytag,但不下传)
    int num;//整个区间被几条互不相交的线段覆盖
    int len;//整个区间被覆盖的总长度
    bool lflag,rflag;
}sh[N*4];
void pushup(int x,int l,int r)
{
    if(sh[x].sum)
    {
        sh[x].num=1;
        sh[x].len=r-l+1;
        sh[x].lflag=sh[x].rflag=1;
    }
    else if(l==r)
    {
        sh[x].num=0;
        sh[x].len=0;
        sh[x].lflag=sh[x].rflag=0;
    }
    else 
    {
        sh[x].num=sh[x<<1].num+sh[x<<1|1].num;
        if(sh[x<<1].rflag&&sh[x<<1|1].lflag)sh[x].num--;
        sh[x].len=sh[x<<1].len+sh[x<<1|1].len;
        sh[x].lflag=sh[x<<1].lflag;
        sh[x].rflag=sh[x<<1|1].rflag;
    }
}
void modify(int x,int l,int r,int lt,int rt,int w)
{
    if(lt<=l&&rt>=r)
    {
        sh[x].sum+=w;
        pushup(x,l,r);
        return ;
    }
    int mid=(l+r)>>1;
    if(lt<=mid)modify(x<<1,l,mid,lt,rt,w);
    if(rt>mid)modify(x<<1|1,mid+1,r,lt,rt,w);
    pushup(x,l,r);
    return;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x1,y1,x2,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        bi[++cnt].h=y1,bi[cnt].w=1,bi[cnt].x=x1,bi[cnt].y=x2;
        bi[++cnt].h=y2,bi[cnt].w=-1,bi[cnt].x=x1,bi[cnt].y=x2;
        mx=max(mx,x2);
        mi=min(mi,x1);
    }
    if(mi<0)
    {
        for(int i=1;i<=cnt;i++)
        {
            bi[i].x+=-mi+1;
            bi[i].y+=-mi+1;//加一与mx相匹配
        }
        mx-=mi;
    }
    sort(bi+1,bi+cnt+1,cmp);
    for(int i=1;i<=cnt;i++)
    {
        modify(1,mi,mx,bi[i].x,bi[i].y-1,bi[i].w);//减一是因为这是长度为1的叶子的右端点,叶子的编号是左端点即右端点-1.
        while(bi[i+1].h==bi[i].h&&bi[i+1].w==bi[i].w)
        {
            i++;
            modify(1,mi,mx,bi[i].x,bi[i].y-1,bi[i].w);
        }//同一类型一起处理
        ans+=abs(sh[1].len-las);//覆盖长度的变化
        las=sh[1].len;
        ans+=sh[1].num*2*(bi[i+1].h-bi[i].h);//垂直的边的长度
    }
    printf("%d",ans);
    return 0;
}

面积并

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
#define int long long
int n,ans,xx[N],cnt,mx=1e9+7,mi=0,las=0;
struct node
{
    int h,x,y,w;
}bi[N*2];
bool cmp(node x,node y)
{
    return x.h<y.h;
}
struct stu
{
    int sum;//整个区间被整体覆盖了几次(类似lazytag,但不下传)
    int len;//整个区间被覆盖的总长度
}sh[N*4];
void pushup(int x,int l,int r)
{
    if(sh[x].sum)sh[x].len=xx[r+1]-xx[l];
    else if(l==r)sh[x].len=0;
    else sh[x].len=sh[x<<1].len+sh[x<<1|1].len;//除了覆盖长度外都不要了
}
void modify(int x,int l,int r,int lt,int rt,int w)
{
    if(lt<=l&&rt>=r)
    {
        sh[x].sum+=w;
        pushup(x,l,r);
        return ;
    }
    int mid=(l+r)>>1;
    if(lt<=mid)modify(x<<1,l,mid,lt,rt,w);
    if(rt>mid)modify(x<<1|1,mid+1,r,lt,rt,w);
    pushup(x,l,r);
    return;
}
signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        int x1,y1,x2,y2;scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
        if(x1>x2)swap(x1,x2);
        if(y1>y2)swap(y1,y2);
        bi[++cnt].h=y1,bi[cnt].w=1,bi[cnt].x=x1,bi[cnt].y=x2;
        bi[++cnt].h=y2,bi[cnt].w=-1,bi[cnt].x=x1,bi[cnt].y=x2;
        mx=max(mx,bi[cnt].y);
        mi=min(mi,bi[cnt].x);
        xx[i]=bi[cnt].x;
		xx[i+n]=bi[cnt].y;
    }
    sort(xx+1,xx+2*n+1);
    int tot=unique(xx+1,xx+2*n+1)-xx-2;
    sort(bi+1,bi+cnt+1,cmp);
    for(int i=1;i<=cnt;i++)
    {
        int x=lower_bound(xx+1,xx+tot+2,bi[i].x)-xx;
        int y=lower_bound(xx+1,xx+tot+2,bi[i].y)-xx-1;//离散化了
        modify(1,1,tot,x,y,bi[i].w);
        ans+=sh[1].len*(bi[i+1].h-bi[i].h);
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2024-08-28 21:35  storms11  阅读(6)  评论(0编辑  收藏  举报