1. 程式人生 > >POJ 1542 Atlantis(線段樹 面積並+離散化)

POJ 1542 Atlantis(線段樹 面積並+離散化)

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=1542
參考網址:http://blog.csdn.net/sunmenggmail/article/details/7984589

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 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 file 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
Sample Output Test case #1 Total explored area: 180.00
Source
Recommend linle   |   We have carefully selected several similar problems for you:  
1828
 1255 1698 1540 1754

題意:題意很簡單,就是求矩形面積的並

給定一個矩形的左下角座標和右上角座標分別為:(x1,y1)、(x2,y2),對這樣的一個矩形,我們構造兩條線段,一條定位在x1,它在y座標的區間是[y1,y2],並且給定一個cover域值為1;另一條線段定位在x2,區間一樣是[y1,y2],給定它一個cover值為-1。根據這樣的方法對每個矩形都構造兩個線段,最後將所有的線段根據所定位的x從左到右進行排序。

上圖中,紅色的字體表示的是該線段的左右標誌,綠色字型為當前更新到當前線段的cover值。剛剛開始的時候,線段樹上的cover值都為0,但第一根線段(x==0)插入線段樹的之後,我們將線段樹上的cover加上該線段的cover,那麼,此時線段樹上被該線段覆蓋的位置上的cover的值就為1,下次再插入第二根線段(x==1)此時發現該線段所覆蓋的區間內,有一部分線段樹的cover為0,另有一部分為1,仔細觀察,但插入第二個線段的時候,如果線段樹上cover已經為1的那些區間,和現在要插入的第二根線段之間,是不是構成了並面積?還不明白?看下圖,綠色部分即為插入第二根線段後得到的並面積

#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=110;
struct LINE
{
    double  x, y_down, y_up;
    int  flag;
    bool operator<(const LINE &a)const  ///按照x從小到大的順序排序
    {
        return  x<a.x;
    }
}line[2*maxn];

struct TREE
{
    double  x,y_down, y_up;
    int     cover;  ///用以表示加進線段樹中的線段次數
    bool    flag;   ///標記葉子節點
}tree[1000*maxn];

double  y[2*maxn];

void build(int i, int l, int r) ///當前節點下標,l , r 線段樹建立左右線陣列下標
{
       tree[i].x = -1; //-1表示該區間已經沒有線段
       tree[i].cover = 0; //表示該區間上有多少條線段;左邊線段加進去則++,右邊線段加進去則--
       tree[i].y_down = y[l];
       tree[i].y_up = y[r];
       tree[i].flag = false;
       if(l+1==r)
       {
           tree[i].flag = true; //flag==true表示達到了葉子節點
           return;
       }
       int mid=(l+r)>>1;
       build(2*i, l, mid);
       build(2*i+1, mid, r);
}

double insert(int i, double x, double l, double r, int flag) //flag表示為左邊還是右邊
{
    if ( r<=tree[i].y_down || l>=tree[i].y_up )   return 0;
    if (tree[i].flag) /// 葉子節點
    {
        if (tree[i].cover > 0) /// 該區域的面積存在,且未經計算
        {
             double temp_x = tree[i].x;
             double ans=( x-temp_x )*(tree[i].y_up - tree[i].y_down);
             tree[i].cover += flag;
             tree[i].x = x;   //定位上一次的x
             return ans;
        }
        else  ///雖然是葉子節點,但是需要更新當前的線段覆蓋標記
        {
            tree[i].cover += flag;
            tree[i].x = x;  ///更新最新x
            return 0;
        }
    }
    return insert(2*i, x, l, r, flag)+insert(2*i+1, x, l, r, flag); ///不是葉子節點就往下遞迴
}

int main( )
{
   // freopen("d:\\in.txt","r",stdin);
    int  Case=0,n,index;
    double  x1, y1, x2, y2;
    while(~scanf("%d",&n) && n)
    {
        index = 1;
        for (int i=1; i<=n; i++)
        {
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            y[index] = y1;
            line[index].x = x1;
            line[index].y_down = y1;
            line[index].y_up = y2;
            line[index++].flag = 1; //1表示左邊

            y[index] = y2;
            line[index].x = x2;
            line[index].y_down = y1;
            line[index].y_up = y2;
            line[index++].flag = -1; //-1表示右邊
        }
        sort(&y[1], &y[index]); //把所有的縱座標按從小到大排序,把1寫成了0,WA一次
        sort(&line[1], &line[index]);
        build(1, 1, index-1);
        double ans=0;
        for (int i=1;i<index; i++) ///將線line從左向右遍歷
            ans+=insert(1, line[i].x, line[i].y_down, line[i].y_up, line[i].flag);
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++Case, ans);
    }
    return 0;
}

開始感覺和POJ 2528應該是一樣的啊,為什麼沒有去重啊,於是寫一個去重的,果斷AC,之後想為什麼?其實也很簡單,因為在這裡的是通過ans=( x-temp_x )*(tree[i].y_up - tree[i].y_down)這個公式求面積的,並且在Build的時候是排過序的,所以兩個相鄰的也就是葉子節點的(tree[i].y_up - tree[i].y_down)如果有重點就為0了,這樣該段算出來的面積也就是0了,沒有影響,但是感覺還是先去重之後比較清晰。

#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int maxn=110;
struct LINE
{
    double  x, y_down, y_up;
    int  flag; ///表示一個矩形的左邊還是右邊
    bool operator<(const LINE &a)const  ///按照x從小到大的順序排序
    {
        return  x<a.x;
    }
}line[2*maxn];


struct TREE
{
    double  x,y_down, y_up;
    int     cover;  ///用以表示加進線段樹中的線段次數
    bool    flag;   ///標記葉子節點
}tree[1000*maxn];


double  y[2*maxn];

void build(int i, int l, int r) ///當前節點下標,l , r 線段樹建立左右線陣列下標
{
   tree[i].x = -1; //-1表示該區間已經沒有線段
   tree[i].cover = 0; //表示該區間上有多少條線段;左邊線段加進去則++,右邊線段加進去則--

   tree[i].y_down = y[l];  ///離散化
   tree[i].y_up = y[r];    ///離散化

   tree[i].flag = false;
   if(l+1==r) ///葉子節點是用來對每個y段進行遍歷用的
   {
       tree[i].flag = true; //flag==true表示達到了葉子節點
       return;
   }

   int mid=(l+r)>>1;
   build(2*i, l, mid);
   build(2*i+1, mid, r);
}

double insert(int i, double x, double l, double r, int flag) //flag表示為左邊還是右邊
{         ///父節點  當前線段的x值 y_Down   y_Up
    if ( r<=tree[i].y_down || l>=tree[i].y_up )   return 0;
    if (tree[i].flag) /// 葉子節點
    {
        double ans = 0;
        if (tree[i].cover > 0) /// 該區域的面積存在,且未經計算
        {
             double temp_x = tree[i].x;
             ans=( x-temp_x )*(tree[i].y_up - tree[i].y_down);
        }
         ///更新該線段最新的x座標和被覆蓋情況
         tree[i].cover += flag;
         tree[i].x = x;   //定位上一次的x

         return ans;
    }
    return insert(i<<1, x, l, r, flag)+insert(i<<1|1, x, l, r, flag); ///不是葉子節點就往下遞迴
}

int main( )
{
   // freopen("d:\\in.txt","r",stdin);
    int  Case=0,n,index;
    double  x1, y1, x2, y2;
    while(~scanf("%d",&n) && n)
    {
        index = 1;
        for (int i=1; i<=n; i++)
        {
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            y[index] = y1;
            line[index].x = x1;
            line[index].y_down = y1;
            line[index].y_up = y2;
            line[index].flag = 1; //1表示左邊
            index++;

            y[index] = y2;
            line[index].x = x2;
            line[index].y_down = y1;
            line[index].y_up = y2;
            line[index].flag = -1; //-1表示右邊
            index++;
        }
        sort(&y[1], &y[index]); //把所有的縱座標按從小到大排序,把1寫成了0,WA一次
        sort(&line[1], &line[index]);

        int nCount=unique(&y[1],&y[index])-(&y[1]);
        build(1, 1, nCount);

        double ans=0;
        for (int i=1;i<index; i++) ///將線line從左向右遍歷
            ans+=insert(1, line[i].x, line[i].y_down, line[i].y_up, line[i].flag);
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++Case, ans);
    }
    return 0;
}