POJ 1151 Atlantis 求矩形并(附个交)的面积 (线段树+离散化)

Atlantis

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 7646    Accepted Submission(s): 3368


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
 
题意:给你n个矩形,左下角和右上角坐标,求矩形面积并。
 
思路:这是去年暑假开的线段数专题里算比较复杂的一题。当时比较弱...看题解都看不懂..现在总算懂了,自己再写了遍。 = =

给定一个矩形的左下角坐标和右上角坐标分别为:(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的那些区间,和现在要插入的第二根线段之间,是不是构成了并面积?还不明白?看下图,绿色部分即为插入第二根线段后得到的并面积

够清楚了吧!也就是说,我们插入某跟线段的时候,只要看该线段所在区间上的cover是否大于等于1,如果是,那么就可以将并面积值加上(目前线段的x定位 - 上一线段的x定位)*(该区间的大小)

代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;

#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , mid
#define rson rs , mid + 1 , r
#define root 1 , 1 , n
#define rt o , l , r

const int N = 1010;
const int inf = 100000000;
struct node
{
    double y_down, y_up, x;
    int cover;
    bool flag;
} s[N*4];

struct Line
{
    double  x, y_down, y_up;
    int  flag;
    bool operator<(const Line &a)const
    {
        return  x<a.x;
    }
} line[2*N];

double  y[2*N];

//bool cmp(Line a,Line b){ return a.x < b.x;}
void build (int o , int l , int r)
{
    s[o].x = -1;
    s[o].cover = 0;
    s[o].y_down = y[l];
    s[o].y_up = y[r];
    s[o].flag = false;
    if (l+1 == r)//这里的叶子节点包含2个点,也就是一单位线段
    {
        s[o].flag = true;
        return;
    }
    int mid = ( l + r ) >> 1;
    build(ls,l,mid);
    build(rs,mid,r);
}
double update(int o, double l, double r, double x, int flag)
{
    if(r <= s[o].y_down || l >= s[o].y_up)
        return 0;
    if(s[o].flag)
    {
        if(s[o].cover > 0)
        {
            double ans = (x - s[o].x)*(s[o].y_up - s[o].y_down);
            s[o].x = x;
            s[o].cover += flag;
            return ans;
        }
        else
        {
            s[o].x = x;
            s[o].cover += flag;
            return 0;
        }
    }
    double ans1,ans2;
    ans1 = update(ls, l, r, x, flag);
    ans2 = update(rs, l, r, x, flag);
    return ans1+ans2;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int  count=0,n,index;
    double x1,y1,x2,y2;
    while(scanf("%d", &n)!= EOF &&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);
        sort(line+1, line+index);
        build(11, index-1);
        double ans=0;
        for (int i=1; i<index; i++)
        {
            ans += update(1, line[i].y_down, line[i].y_up, line[i].x, line[i].flag);
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++count, ans);
    }
    return 0;
}
View Code
 

第二种方法   (是按x切割的逐块长方形面积)

该方法同样需要在线段树中定义一个cover域,表示该线段区间目前被覆盖的线段数目。另外再加一个len域,表示该区间可用于与下一线段求并面积的y坐标区间长度。然后利用简单的dp,将所有信息集中于tree[1].len上,这样便不用想第一种方法那样每次都求到叶子线段,大大节约了时间,并且代码也少了很多。

 

代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
    int l;
    int r;
    int cover;
    double len;
};

node tree[2000];
double yy[250];
int n,len;

struct Line
{
    double y_down;
    double y_up;
    double x;
    int cover;
};

Line line[250];

int cmp(Line a,Line b){return a.x<b.x;}

int find(double x)
{
    int l=0,r=len,mid;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(yy[mid]==x)
            return mid;
        if(yy[mid]<x)
            l=mid+1;
        else
            r=mid-1;
    }
    return l;
}

void build(int i,int l,int r)
{
    tree[i].l=l;
    tree[i].r=r;
    tree[i].cover=0;
    tree[i].len=0;
    if(l+1==r)
        return;
    int mid=(l+r)/2;
    build(2*i,l,mid);
    build(2*i+1,mid,r);
}

void fun(int i)
{
    if(tree[i].cover)
        tree[i].len=yy[tree[i].r]-yy[tree[i].l]; //如果cover大于1,那么整段都可用于与下一线段求并面积
    else if(tree[i].l+1==tree[i].r) //叶子线段
        tree[i].len=0;
    else
        tree[i].len=tree[2*i].len+tree[2*i+1].len; //很简单的dp
}

void updata(int i,int l,int r,int cover)
{
    if(tree[i].l>r || tree[i].r<l)
        return;
    if(tree[i].l>=l && tree[i].r<=r)
    {
        tree[i].cover+=cover;
        fun(i);
        return;
    }
    updata(2*i,l,r,cover);
    updata(2*i+1,l,r,cover);
    fun(i);
}

int main()
{
    freopen("in.txt","r",stdin);
    double x1,y1,x2,y2;
    int i,m,a,b,cas=1;
    freopen("in.txt","r",stdin);
    while(scanf("%d",&n)==1 && n)
    {
        m=0;
        for(i=0;i<n;i++)
        {
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            yy[m]=y1;
            line[m].cover=1;
            line[m].x=x1;
            line[m].y_down=y1;
            line[m++].y_up=y2;

            yy[m]=y2;
            line[m].cover=-1;
            line[m].x=x2;
            line[m].y_down=y1;
            line[m++].y_up=y2;
        }
        sort(yy,yy+m);
        len=1;
        for(i=1;i<m;i++)
        {
            if(yy[i-1]!=yy[i])
                yy[len++]=yy[i];
        }
        len--;
        build(1,0,len);
        sort(line,line+m,cmp);
        double ans=0;
        printf("Test case #%d\n",cas++);
        for(i=0;i<m-1;i++)
        {
            a=find(line[i].y_down);
            b=find(line[i].y_up);
            updata(1,a,b,line[i].cover);
            ans+=tree[1].len*(line[i+1].x-line[i].x);  //tree[1].len已经保留了整个树与line[i+1]所能求并面积的长度
        }
        printf("Total explored area: %0.2lf\n\n",ans);
    }
    return 0;
}
View Code

 

 顺便贴个矩形面积交的代码...很方便...我的代码是每次都更新到叶子节点的..修改起来也很方便...当然效率不是很高..AC足够了
代码://hdu 1255   1092ms   1492k
#include<cmath>
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;

#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , mid
#define rson rs , mid + 1 , r
#define root 1 , 1 , n
#define rt o , l , r

const int N = 10100;
const int inf = 100000000;
struct node
{
    double y_down, y_up, x;
    int cover;
    bool flag;//叶子节点
} s[N*4];

struct Line
{
    double  x, y_down, y_up;
    int  flag;
    bool operator<(const Line &a)const
    {
        return  x<a.x;
    }
} line[2*N];

double  y[2*N];

//bool cmp(Line a,Line b){ return a.x < b.x;}
void build (int o , int l , int r)
{
    s[o].x = -1;
    s[o].cover = 0;
    s[o].y_down = y[l];
    s[o].y_up = y[r];
    s[o].flag = false;
    if (l+1 == r)//这里的叶子节点包含2个点,也就是一单位线段
    {
        s[o].flag = true;
        return;
    }
    int mid = ( l + r ) >> 1;
    build(ls,l,mid);
    build(rs,mid,r);
}
double update(int o, double l, double r, double x, int flag)
{
    if(r <= s[o].y_down || l >= s[o].y_up)
        return 0;
    if(s[o].flag)
    {
        if(s[o].cover >= 2)//这里修改
        {
            double ans = (x - s[o].x)*(s[o].y_up - s[o].y_down);
            s[o].x = x;
            s[o].cover += flag;
            return ans;
        }
        else
        {
            s[o].x = x;
            s[o].cover += flag;
            return 0;
        }
    }
    double ans1,ans2;
    ans1 = update(ls, l, r, x, flag);
    ans2 = update(rs, l, r, x, flag);
    return ans1+ans2;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int n,index,t;
    double x1,y1,x2,y2;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d", &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);
        sort(line+1, line+index);
        build(11, index-1);
        double ans=0;
        for (int i=1; i<index; i++)
        {
            ans += update(1, line[i].y_down, line[i].y_up, line[i].x, line[i].flag);
        }
        printf("%.2f\n",ans);
    }
    return 0;
}
View Code
posted @ 2015-02-23 12:31  Doli  阅读(279)  评论(0)    收藏  举报