1. 程式人生 > >POJ 1151 Atlantis 線段樹+離散化+掃描線 (java實現)

POJ 1151 Atlantis 線段樹+離散化+掃描線 (java實現)

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();
    }
}