• 西安段素扫描线


    如果我们要统计一个由多个矩形重叠组成的图形的面积。

    暴力太麻烦,而计算机又不能想人一样计算,那怎么求解呢?

    我们可以使用扫描线fa

    想象一下,有一条线,按照一个顺序(从左到右呀,从上到下呀...)扫描一个图形。

    我们很容易可以得到,两条最近的相邻线段间,所包含的这一个图形的面积是规整的矩形,又因为这些矩形的长or宽我们已经知道(就是数据中给定矩形的边),所以我们只需要统计另一条边就可以了。

    维护线段,我们就可以使用西安段素了。

    同一个矩形,在遇到他的第一条和扫描线(是一个线段树)平行的的边时,在这条边和扫描线重叠的部分上累加1(一定是累加),然后继续往后扫描,遇到其他矩形的第一条边时亦是。

    知道碰到某个矩形的第二条边,在减去。

    那在扫描中怎么算面积呢? 我们需要统计的是扫描线上有多少长度被覆盖,然后乘上两条线段之间的垂直距离就可以了。

    那统计周长怎么算呢?

    (现在都是从下往上扫)先来看平行于扫描线的边。

    两次扫描后,扫描线上的被覆盖长度的差的绝对值就是所增加的长度。

    为什么是差呢?又为什么是绝对值呢?

    我们通过一张图来解释:

    可以看出,这是两个矩形嵌套,然后自己在宽扁的矩形的左边第一条边的扫描线上的被覆盖的长度,和另一个矩形的第一条边时的差就是第二个矩形延展出来的长度,然后绝对值的意思是当一个矩形结束时,他有可能会与他有重叠的矩形凹陷,然后绝对值就将这种情况的边取了出来。

    那于扫描线垂直的呢?

    我们可以发现,两次扫描中间的那些与扫描线垂直的线段,他的长度都是两次扫描之间的距离,然后我们就需要统计他的个数

    可以发现, 两次扫描中,有多少个不相连的空隙,就有相同个数*2条如此线段

    然后具体实现会从详解。请先从main()看起

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    const int manx=5010;
    struct Edge
    {
    	int x1,x2;
    	int y;
    	int f;
    	void change()
    	{
    		if(x1>x2)
    			swap(x1,x2);
    		return ;
    	}
    };
    struct node
    {
    	int f;//有多少条不想交的线段
    	int ha;//是否被整体覆盖,不下传
    	int len;//总共长度
    	int fl,fr;//左右端点是否被覆盖,在合并时会用到
    	int l,r;//实际左右端点
    };
    int te,tx,k;
    Edge line[manx<<1];
    int base[manx<<1];
    int datx[manx<<1];
    node t[manx<<5];
    void add(int a,int b,int c,int d)
    {
    	if(b>d)	swap(b,d);
    	int x1=min(a,c),x2=max(a,c);
    	line[++te].x1=x1;
    	line[te].x2=x2;//x1,x2为左右端点,y为横坐标
    	line[te].y=b;
    	line[te].f=1;//这里是精髓,下边是一个矩阵的开始,上边是一个矩阵的结束,如此处理,我们到时候就可以直接传进更新扫描线的函数里,就不用判断了
    	line[++te].x1=x1;
    	line[te].x2=x2;
    	line[te].y=d;
    	line[te].f=-1;
    }
    bool compare(const Edge &a,const Edge &b)
    {
    	return a.y==b.y ? a.f > b.f : a.y < b.y;
    }
    void make_base()
    {
    	int now=0x7fffffff;
    	for(int i=1;i<=tx;i++)
    		if(now!=datx[i])
    		{
    			now=datx[i];
    			base[++k]=now;
    		}//简简单单的去重
    	return ;
    }
    void build(int root,int l,int r)
    {
    	t[root].f=0;t[root].len=0;
    	t[root].fl=t[root].fr=0;
    	t[root].l=base[l];t[root].r=base[r+1];//因为是存的是节点之间的间隙,而且离散化了,所以说在处理左右的时候一定要小心,至于为什么r要+1,因为我是第i个点后的间隙的标号为i
    	if(l==r)	return ;//剩下的就很普通了
    	int mid=(l+r)>>1;
    	build(root<<1,l,mid);
    	build(root<<1|1,mid+1,r);
    	return ;
    }
    int check(int val)
    {
    	int l=1,r=k;
    	int mid;
    	while(l<r)
    	{
    		mid=(l+r)>>1;
    		if(base[mid]<val)	l=mid+1;
    		else	r=mid;
    	}
    	return l;
    }
    void push_up(int root,int l,int r)
    {
    	if(t[root].ha)//如果这个节点被整体覆盖,ha永远也不会变成负数。为什么,可以自己小小的思考一下
    	{
    		t[root].len=t[root].r-t[root].l;//计算长度
    		t[root].fl=t[root].fr=1;//左右端点都被覆盖
    		t[root].f=1;//其中有一条线段
    		return ;
    	}//此处永远不会出现l==r的情况
    	else//一般情况
    	{
    		t[root].len=t[root<<1].len+t[root<<1|1].len;//合并
    		t[root].fl=t[root<<1].fl;//大区间的左端点
    		t[root].fr=t[root<<1|1].fr;//大区间的右端点
    		t[root].f=t[root<<1].f+t[root<<1|1].f-(t[root<<1].fr&t[root<<1|1].fl);//如果左儿子的右端点和右儿子的左端点都被覆盖,那条数就要减1
    		return ;
    	}
    }
    void updata(int root,int l,int r,int al,int ar,int k)
    {
    	if(l>ar||r<al)	return ;
    	if(l>=al&&r<=ar)
    	{
    		t[root].ha+=k;//蛤呸,ha为被整体覆盖的次数,有可能被多次覆盖
    		push_up(root,l,r);//更新
    		return ;
    	}
    	int mid=(l+r)>>1;
    	updata(root<<1,l,mid,al,ar,k);
    	updata(root<<1|1,mid+1,r,al,ar,k);
    	push_up(root,l,r);//合并,这里没有push_down
    }
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	int a,b,c,d;
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d%d%d%d",&a,&b,&c,&d);
    		add(a,b,c,d);//保存边,我们这里只需要存与我们扫描线平行的边,然后我是自下而上的。所以我存的是上下边,然后请仔细看add函数   -》add
    		datx[++tx]=a,datx[++tx]=c;//用于离散化的数组,当然数据小的话可以不离散化
    	}
    	sort(line+1,line+1+te,compare);//排序,有重边一定要下边先被处理
    	sort(datx+1,datx+1+tx);//离散化
    	make_base();//真离散化
    	build(1,1,k-1);//建树,注意k为离散化后的坐标个数,这里要建立k-1个叶子节点,线段树中存的是相邻两个坐标之间的间隙
    	int ans=0; //周长
    	int last=0;//用于记录两次扫描的被覆盖的长度的变量
    	for(int i=1;i<=te;i++)
    	{
    		int pos1=check(line[i].x1),pos2=check(line[i].x2);//二分查找离散化后的位置
    		updata(1,1,k-1,pos1,pos2-1,line[i].f);//更新,这里就体现出我们标记上下边的好处了
    		int pas=t[1].len;//最近一次扫描的长度
    		ans+=abs(last-pas);//处理平行边
    		ans+=t[1].f*2*(line[i+1].y-line[i].y);//处理垂直边,一定要与后一个待扫描线之间的距离
    		last=pas; //迭代
    	}
    	printf("%d",ans);
    }
    
    
  • 相关阅读:
    application.properties多环境配置文件、jar包外部配置文件、配置项加密、程序中配置使用
    SpringBoot项目打war包部署Tomcat教程
    spring boot 集成 redis lettuce
    spring boot maven打包可运行jar包
    IDEA项目搭建十四——Web站点Controller基类及布局页静态资源设计
    Nginx Windows详细安装部署教程
    多线程编程CompletableFuture与parallelStream
    IDEA项目搭建十三——服务消费端与生产端通信实现
    【异常】MySQL建表报错:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"order"' at line 1
    【警告】mysql链接警告信息:Establishing SSL connection without server's identity verification is not recommended
  • 原文地址:https://www.cnblogs.com/Lance1ot/p/9204592.html
Copyright © 2020-2023  润新知