POJ1151Atlantis 矩形面积并 扫描线 线段树

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - POJ1151


题意概括

  给出n个矩形,求他们的面积并。

  n<=100


题解

  数据范围极小。

  我们分3种算法逐步优化。

  算法1: O(n3)

  如果这n个矩形的坐标都是整数,而且比较小,那么我们显然可以用最暴力的方法:一个一个打标记。

  但是不是这样的。

  坐标大小很大,而且是实数。

  然而我们发现差不多,只要先离散化一下,然后再打标记即可。

  算法2:O(n2)

  实际上,上面的方法十分慢。如果n的范围到了1000,上面的就无济于事了。

  而实际上,基于上面的打标记的算法,我们可以通过差分的方法n2解决。

  我们通过差分,可以用n2的时间标记,n2的时间判断每一个区域是否被覆盖。

  空间复杂度O(n2)

  算法3:O(n logn) 扫描线

  实际上,这类问题的数据范围可以到100000这个级别。

  矩形面积并可以用扫描线算法来解决。先看原理,后面讲具体实现。

  比如下图:

  

  当前我们的扫描线到达了淡黄色部分。

  由于之前没有记录,所以答案不增加。

  然而我们记下当前横向覆盖的长度。

  然后我们到了第二条扫描线,加上原来记录的横向覆盖长度乘以增加的高度就是当前增加的答案。

  然后,我们更新了横向覆盖的长度。

  继续。

  

  然后第三条。现在的横向覆盖长度是两边加起来,所以增加的面积是两块了。

  然后更新横向覆盖的长度,加上了中间的那一条。

  然后继续。

  

  现在有这么长的一条都是被横向覆盖的了。

  所以新增的面积是浅蓝色部分。

  然后我们发现左上那条线是出边,所以要删除这一条线。

  所以横向覆盖的长度为如下:

  

  同理,接下来是:

  

  然后就OK了。

  

  那么具体怎么实现呢?

  我们开一棵线段树来维护!

  在读入之后,我们把所有的横线都拆开,分成下边和上边两类。某一区间在进入下边的时候+1,离开上边的时候-1,所以我们分别给上下边标记+1和-1。

  对于Y,我们离散化一下。

  对于X,我们按照边的X排一个序。

  然后按照刚才那样的处理。

  具体如何维护详见代码。


代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
const int N=100+5,M=N*2;
const double Eps=1e-9;
int T=0,n,m,tot_Y,tot_s;
double Y[M];
struct Segment{
    double x,L,R;
    int v;
    void set(double x_,double L_,double R_,int v_){
        x=x_,L=L_,R=R_,v=v_;
    }
}s[M];
struct SegTree{
    int cnt;
    double sum;
}t[M*4];
bool cmp_s(Segment a,Segment b){
    return a.x<b.x;
}
void build(int rt,int le,int ri){
    t[rt].cnt=0;
    t[rt].sum=0;
    if (le==ri)
        return;
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    build(ls,le,mid);
    build(rs,mid+1,ri);
}
void pushup(int rt,int le,int ri){
    int ls=rt<<1,rs=ls|1;
    if (t[rt].cnt)
        t[rt].sum=Y[ri+1]-Y[le];
    else if (le==ri)
        t[rt].sum=0;
    else
        t[rt].sum=t[ls].sum+t[rs].sum;
}
void update(int rt,int le,int ri,int xle,int xri,int d){
    if (le>xri||ri<xle)
        return;
    if (xle<=le&&ri<=xri){
        t[rt].cnt+=d;
        pushup(rt,le,ri);
        return;
    }
    int mid=(le+ri)>>1,ls=rt<<1,rs=ls|1;
    update(ls,le,mid,xle,xri,d);
    update(rs,mid+1,ri,xle,xri,d);
    pushup(rt,le,ri);
}
int find_double(double x){
    int le=1,ri=m,mid;
    while (le<=ri){
        mid=(le+ri)>>1;
        if (abs(x-Y[mid])<Eps)
            return mid;
        if (Y[mid]<x)
            le=mid+1;
        else
            ri=mid-1;
    }
}
int main(){
    while (scanf("%d",&n)&&n){
        tot_Y=tot_s=0;
        for (int i=1;i<=n;i++){
            double xA,yA,xB,yB;
            scanf("%lf%lf%lf%lf",&xA,&yA,&xB,&yB);
            if (yB-yA<Eps||xB-xA<Eps)
                continue;
            Y[++tot_Y]=yA,Y[++tot_Y]=yB;
            s[++tot_s].set(xA,yA,yB,1);
            s[++tot_s].set(xB,yA,yB,-1);
        }
        sort(Y+1,Y+tot_Y+1);
        sort(s+1,s+tot_s+1,cmp_s);
        m=1;
        for (int i=2;i<=tot_Y;i++)
            if (Y[i]-Y[i-1]>Eps)
                Y[++m]=Y[i];
        build(1,1,m);
        double ans=0;
        for (int i=1;i<=tot_s;i++){
            ans=ans+(s[i].x-s[i-1].x)*t[1].sum;
            int L=find_double(s[i].L);
            int R=find_double(s[i].R);
            update(1,1,m,L,R-1,s[i].v);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++T,ans);
    }
    return 0;
}
时间: 2024-10-01 06:00:18

POJ1151Atlantis 矩形面积并 扫描线 线段树的相关文章

HDU 1255 覆盖的面积 (扫描线 线段树 离散化)

题目链接 题意:中文题意. 分析:纯手敲,与上一道题目很相似,但是刚开始我以为只是把cnt>=0改成cnt>=2就行了,. 但是后来发现当当前加入的线段的范围之前 还有线段的时候就不行了,因为虽然现在都不等于 2,但是之前的那个线段加上现在的已经覆盖2次了. 1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4 #include <cstring> 5 #include &

poj 1151 求矩形面积并 (线段树扫描线)

题意: 给出n个矩形的左下角和右上角坐标,求这n个矩形所构成的面积 思路: 线段树扫描线 这是第一次做到线段树扫描线,刚开始也不懂 如果不懂,可以看: http://www.cnblogs.com/scau20110726/archive/2013/04/12/3016765.html 和 http://www.faceye.net/search/69289.html 我是看第一个链接弄懂的 然后学习了第二位的方法 代码上也有比较详细的注释,想可以帮到大家 code: #include<cstd

求出被矩形覆盖过至少两次的区域的面积(扫描线 + 线段树)

题目链接:https://vjudge.net/contest/332656#problem/J 思路: 这道题的大体的思路其实还是扫描线的思路. 就是我们要清晰之前我们所说的len 代表的是被覆盖了一次及以上次数的线段长度 为叙述方便,我们假设len[2]为当前线段被覆盖了两次的长度,len[1]为当前线段被覆盖了一次的长度,而len[0]就是这条线段的长度,并且满足len[2]+len[1]=len[0]. 首先,如果当前这条线段已经被覆盖了两次了,那么这条线段的len[2]就应该等于len

【HDU 1542】Atlantis 矩形面积并(线段树,扫描法)

[题目] Atlantis Problem Description There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of

USACO5.5 矩形周长 Picture | 扫描线 + 线段树

题目:Luogu 1856 扫描线,将矩形拆成两条边分别为 +1 和 -1,计算无重叠部分的长度. 由于 update 区间 [a, b] 和 [b, c] 时会把 b 加两次,所以统一转换成 [a,b),累加长度的时候再将右端点右移. 需要注意,在覆盖一条边时,不能直接累加边的长度,像下图: 在覆盖边 [1,4] 前,[3,4] 已经累加过了,所以此时应该累计的是 [1,4] 的长度减去已经覆盖过的长度,然后再把 [1,4] 覆盖上: 相应的,在删除 [1,4] 这条边时,累计的是删除 [1,

HDU1542 矩形面积并 扫描线段树

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542 题意:二维平面有n个平行于坐标轴的的矩形,要算矩形面积并 有一个讲的很好的博客:https://blog.csdn.net/u013480600/article/details/22548393 线段树是按叶节点来一个个维护的,一堆浮点数是不能直接维护的,需要离散化来维护. 要注意我们线段树中每个叶节点(控制区间[L,L])不是指X[L]坐标,而是指区间[X[L],X[L+1]].线段树中其他

【BZOJ4418】[Shoi2013]扇形面积并 扫描线+线段树

[BZOJ4418][Shoi2013]扇形面积并 Description 给定N个同心的扇形,求有多少面积,被至少K个扇形所覆盖. Input 第一行是三个整数n,m,k.n代表同心扇形的个数,m用来等分 [-π,π]的弧度. 从第二行开始的n行,每行三个整数r,a1,a2.描述了一个圆心在原点的扇形,半径为r,圆心角是从弧度πa1/m到πa2/m,a1可能大于a2,逆时针扫过的区域为该扇形面积. Output 输出一个整数ans,至少被K个扇形所覆盖的总面积等于π/2m×ans 保证答案不超

[USACO5.5]矩形周长Picture[扫描线+线段树]

题意:给出一些矩阵,求这些矩阵合并后外部(被包括在内部的不算)周长 端点-1这个是用点代替了边,区间内有几个点就代表区间长度是多少 #include <bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; inline void chmax(int &x, int y) {if (x < y) x = y;} inline void chmin(int &x, int y) {if (x >

luogu P1856 [USACO5.5]矩形周长Picture 扫描线 + 线段树

Code: #include<bits/stdc++.h> #define maxn 200007 #define inf 100005 using namespace std; void setIO(string s) { string in=s+".in"; freopen(in.c_str(),"r",stdin); } struct Edge { int l,r,h,flag; }edges[maxn]; int n; namespace tr