zoukankan      html  css  js  c++  java
  • 20201009 day30 复习1:扫描线

    LGP5490

    题意

    给定若干组((x_1,y_1,x_2,y_2)),求所有这样的两点构成的平行于坐标轴矩形的面积之并。

    面积并

    面积的求法:(sumlimits)截线段长度( imes)扫过的高度

    考虑这个图像,如何模拟扫描线从下向上扫过图形,并且快速计算出当前扫描线被截的长度。

    现在假设,扫描线每次会在碰到横边时停下来。如图。

    简单来说,可以对图形面积产生影响的元素,也就是这些横边左右端点的坐标。
    为了快速计算出截线段长度,可以将横边赋值不同的权值。具体为:对于一个矩形,其下边权值为1,下边权值为(-1)

    然后把所有横边按照(y)坐标升序排序。这样,对于每一个矩形,扫描线总是会先碰到下边,再碰到上边。这样可以保证扫描线所截的长度为非负。

    这样操作以后,可以使用线段树。先把所有端点在(x)轴上离散化。

    在上面的例子中,(4)个端点的纵投影总共把(x)轴切割成了5部分。取中间的三部分线段,建立一颗线段树,每一个端点维护一条线段【也就是一个区间】的信息:

    1. 该线段被覆盖了多少次(被多少个矩形所覆盖)
    2. 该线段内被整个图形所截的长度是多少。

      只要一条线段被覆盖,那么它一定被图形所截。这样转化为了一个区间查询问题,即:每次将当前扫描线扫到的边对应的信息按照之前赋值的权值更新,再查询线段树根节点的信息,得到当前扫描线扫过的面积。

    模拟过程:


    上图的5号节点(sum=2),绿色表示已经计算的面积。

    线段树怎么建?

    建树

    void build_tree(int x, int l, int r) {
    //	初始化节点信息
    	if(l == r)
    		return;
    	int mid = (l + r) >> 1;
    	build_tree(lson, l, mid);
    	build_tree(rson, mid + 1, r);
    	return;
    }
    

    这棵线段树的每个节点都对应了一条线段。考虑将线段树上节点对应的区间和横边建立映射
    对于一个叶子节点(x),建树时已经保证(tree_x.l=tree_x.r),但是他保存的信息显然不是某条线段的一个端点。(如果一条线段的两个端点重合,本质上仅是一个点)。再看一个节点的左右儿子,同样的,建树的时候已经保证了左右儿子的区间不会重合(交集为空),但看这样两条相邻线段:([1,2],[2,3]),你会发现([1,2]∩[2,3]={2}),也就是左儿子的右端点和右儿子的左端点其实是重合的。

    考虑把线段树每个节点(x)对应的区间((tree_x.l,tree_x.r))不变,改变区间和横边的映射关系,具体为:节点(x)对应([X[tree_x.l],X[tree_x.r+1]])这条横边。这里将右端点的对应关系更改了。

    code

    #include <cstdio>
    //不开cmath,y1有定义
    #include <algorithm>
    #include <cstring>
    using namespace std;
    const int maxn=1e6+10;
    int n,cnt=0;
    long long x1,y1,x2,y2,X[maxn<<1];
    struct scanline{
    	long long l,r,h;
    	int mark;//保存权值(1/-1)
    	bool operator <(const scanline &rhs) const{
    		return h<rhs.h;
    	}
    }line[maxn<<1];
    struct segtree{
    	int l,r,sum;
    	long long len;
    	//sum:被完全覆盖的次数
    	//len:区间内被截的长度
    }tree[maxn<<2];
    void buildtree(int x,int l,int r){
    	tree[x].l=l,tree[x].r=r;
    	tree[x].len=0;
    	tree[x].sum=0;
    	if(l==r) return ;
    	int mid=(l+r)>>1;
    	buildtree(x<<1,l,mid);
    	buildtree(x<<1|1,mid+1,r);
    	return ;
    }
    void pushup(int x){
    	int l=tree[x].l,r=tree[x].r;
    	if(tree[x].sum)//被覆盖过
    		tree[x].len=X[r+1]-X[l];//更新长度
    	else
    		tree[x].len=tree[x<<1].len+tree[x<<1|1].len;//合并儿子信息
    }
    void edittree(int x,long long L,long long R,int c){
    	int l=tree[x].l,r=tree[x].r;
    	//这里的l,r和L,R意义不同,l,r表示这个节点管辖的下标范围,而L,R表示要修改的区间
    	if(X[r+1]<=L||R<=X[l]) return ;
    	//这里加等号的原因:假设现在考虑[2,5][5,8]两条线段,希望修改[1,5]区间的sum
    	//虽然5在这个区间内,但是[5,8]并不是我们想要修改的线段,所以加上了等号
    	if(L<=X[l]&&X[r+1]<=R){
    		tree[x].sum+=c;
    		pushup(x);
    		return ;
    	}
    	edittree(x<<1,L,R,c);
    	edittree(x<<1|1,L,R,c);
    	pushup(x);
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
    		X[2*i-1]=x1,X[2*i]=x2;
    		line[2*i-1]=(scanline){x1,x2,y1,1};
    		line[2*i]=(scanline){x1,x2,y2,-1};
    		//线段含两个端点,一个矩形的上下边都需要扫过
    	}
    	n<<=1;
    	sort(line+1,line+n+1);
    	sort(X+1,X+n+1);
    	int tot=unique(X+1,X+n+1)-X-1;//去重
    	buildtree(1,1,tot-1);
    	//tot-1的原因:因为右端点的对应关系已经被修改了,[1,tot-1]其实就是[X[1],X[tot]]
    	long long ans=0;
    	for(int i=1;i<n;i++){//最后一条边不管
    		edittree(1,line[i].l,line[i].r,line[i].mark);
    		ans+=tree[1].len*(line[i+1].h-line[i].h);
    	}
    	printf("%lld",ans);
    	return 0;
    }
    
    要做就做南波万
  • 相关阅读:
    第五周作业
    第四周作业
    第三周作业
    第二周作业
    第一周作业
    FileZilla连接centos7失败处理(SSH)
    单例设计模式
    JQuery中的$符号的作用----网摘
    浅谈关于“中文编程”是否会成为中国程序员的一颗“银弹”
    第8周作业 邱鹏 2013551628
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/20201009day30-001.html
Copyright © 2011-2022 走看看