POJ 1151 Atlantis 线段树+离散化+扫描线 (java实现)

5 篇文章 0 订阅
2 篇文章 0 订阅

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 Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity.
Input
The input consists of several test cases. Each test case starts with a line containing a single integer n (1 <= n <= 100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0 <= x1 < x2 <= 100000;0 <= y1 < y2 <= 100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area.
The input file is terminated by a line containing a single 0. Don’t process it.
Output
For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point.
Output a blank line after each test case.
Sample Input
2
10 10 20 20
15 15 25 25.5
0

意思是,求几个矩形面积并查。以前做过这道题,再次做一下并详细解释一下。

以下图转载自@kk303的博客

  • 首先呢,我们需要一根扫描线,这里我们以矩形的高排序,所以我们的扫描线是从下到上的。
    我们设置矩形的下边的标记为1,矩形的上边为-1.扫描到矩形的下边,我们就把对应位置更新为
    1,若扫到矩形的上边-1,说明这矩形已经不在需要了,所以对应位置由1加上-1那就变成了0.具体如图。

  • 扫到第一个矩形下边,对应位置加1.(这里我们采取的离散化,把x坐标离散化成对应数组的位置,如图,例如
    数组10,15,20.第一个矩形的下边左右点的坐标分别是10和20,那么在数组里就是1和3,我们就更新1~3的标记)

  • 扫到第二个矩形的下边,更新标记(2~4).(在上一步我们已经可以把
    绿色矩形的面积加上了,因为我们知道两个矩形的高度差)。

  • 继续扫,更新标记(下边)

  • 这里我们遇到第一个矩形的上边,这时候更新标记,1~3的标记都减掉1,这时候我们会发现1的计数变成0了,那么
    我们下一次更新的时候这段完全就被舍弃了,算面积就不会把这一段算上了,以此类推。

  • 以上过程就是模拟扫描线扫描过程的情况,大概以上就这样,不过我们代码对于线段的重叠需要修改一下。
    不能够像上面那样子直接更新整个区间,具体看代码解释。

  • 我们上面看似1~3每个点都加1,这样只是为了看起来更加直观(代码实现并非这样子),此题因为横坐标包含浮点数,因此先离散化。另外,因为用线段树维护的是覆盖在x轴上的边,而边是连续的,并非是一个个断点,因此线段树的每一个叶子结点实际存储的是该点与下一点之间的距离,所以这里也是我们为什么r要减掉一的原因了。
Problem: 1151       User: ostreamBaba
Memory: 5388K       Time: 516MS
Language: Java      Result: Accepted
Source Code
import java.io.*;
import java.util.*;
public class Main{
    static class Seg implements Comparable<Seg>{
        double l,r,h;
        int f;
        public Seg(double l, double r, double h, int f) {
            this.l = l;
            this.r = r;
            this.h = h;
            this.f = f;
        }
        @Override
        public int compareTo(Seg seg) {
            return Double.compare(h,seg.h);
        }
    }
    public static int lower_bound(double[] array,int l,int r,double key){
        while (l<r){
            int mid=(l+r)>>1;
            if(array[mid]>=key){
                r=mid;
            }else{
                l=mid+1;
            }
        }
        return l;
    }
    public static void pushUp(int l,int r,int rt){
        if(cnt[rt]!=0){ //如果该点是标记的 直接加上这段边的长度
            sum[rt]=x[r+1]-x[l];
        }else if(l==r){ //没有标记到的话 没有孩子节点 长度为0
            sum[rt]=0;
        }else{
            sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //有孩子节点 是左右孩子节点的和
        }
    }
    public static void query(int L,int R,int l,int r,int rt,int f){
        if(L<=l&&r<=R){
            cnt[rt]+=f;
            pushUp(l,r,rt);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid){
            query(L,R,l,mid,rt<<1,f);
        }
        if(R>mid){
            query(L,R,mid+1,r,rt<<1|1,f);
        }
        pushUp(l,r,rt);
    }
    public static Seg[] seg;
    public static double[] sum;
    public static double[] x;
    public static int[] cnt;
    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        Scanner cin=new Scanner(new InputStreamReader(System.in));
        PrintWriter out=new PrintWriter(new BufferedOutputStream(System.out));
        int n;
        int total=1;
        while (cin.hasNext()&&(n=cin.nextInt())!=0){
            int ct=0;
            seg=new Seg[n<<1];
            x=new double[n<<1];
            for(int i=0;i<n;++i){
                double x1=cin.nextDouble(),y1=cin.nextDouble(),x2=cin.nextDouble(),y2=cin.nextDouble();
                seg[ct]=new Seg(x1,x2,y1,1); //矩形的上边
                x[ct++]=x1;
                seg[ct]=new Seg(x1,x2,y2,-1); //矩形的下边
                x[ct++]=x2; //将两个x坐标加入x数组中做离散化准备
            }
            Set<Double> set=new TreeSet<Double>(); //利用TreeSet来离散化
            for(double t:x){
                set.add(t);
            }
            int k=0;
            for(double t:set){
                x[k++]=t;
            }
            Arrays.sort(seg); //对矩形的上下边进行排序,以高作为排序基准
            //java不需要建树,自动初始化为0,c++需要建树或者初始化为0
            sum=new double[k<<2]; 
            cnt=new int[k<<2];
            double ans=0;
            for(int i=0;i<ct-1;++i){ 
                int l=lower_bound(x,0,k-1,seg[i].l);
                int r=lower_bound(x,0,k-1,seg[i].r)-1;
                query(l,r,0,k-1,1,seg[i].f);
                ans+=sum[1]*(seg[i+1].h-seg[i].h);
            }
            if(total!=1){
                out.println();
            }
            out.printf("Test case #%d\n",total++);
            out.printf("Total explored area: %.2f\n",ans);
        }
        out.flush();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值