poj 1177 线段树+离散化+扫描线 求矩形的周长和

Picture
Time Limit: 2000MS Memory Limit: 10000K
Total Submissions: 10230 Accepted: 5422

Description

A number of rectangular posters, photographs and other pictures of the same shape are pasted on a wall. Their sides are all vertical or horizontal. Each rectangle can be partially or totally covered by the others. The length of the boundary of the union of all rectangles is called the perimeter. 

Write a program to calculate the perimeter. An example with 7 rectangles is shown in Figure 1. 

The corresponding boundary is the whole set of line segments drawn in Figure 2. 

The vertices of all rectangles have integer coordinates. 

Input

Your program is to read from standard input. The first line contains the number of rectangles pasted on the wall. In each of the subsequent lines, one can find the integer coordinates of the lower left vertex and the upper right vertex of each rectangle. The values of those coordinates are given as ordered pairs consisting of an x-coordinate followed by a y-coordinate. 

0 <= number of rectangles < 5000 
All coordinates are in the range [-10000,10000] and any existing rectangle has a positive area.

Output

Your program is to write to standard output. The output must contain a single line with a non-negative integer which corresponds to the perimeter for the input rectangles.

Sample Input

7
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4
2 15 10 22
30 10 36 20
34 0 40 16

Sample Output

228

Source


  首先说离散化:因为有的时候题目中给出的数据范围可能非常大,如果直接建立成线段树的话,绝对超内存,怎么办呢?其实这里所谓离散化就是把这些数排列在数组中,然后用数组的下标来代替这个数,这样我们最多只有N*2个点

   如何求周长? 那么现在我假设读者都明白离散化,以及线段树了!

  具体做法有两种形式  可以对x离散也可以对y 离散! 我这里以对y  进行离散(这样可能更符和我们平常的思维方式)

1:首先将矩形的竖边全部存起来(要用一个标记变量标记该边是否为入边),然后按照竖边的X 的大小排好序;

2:在存储矩形竖边的同时要把矩形的Y坐标存起来,然后排序,最后去掉重复的点!

3:建树根据第二步中最后留下来点的个数num建立一个区间长度为[0,num-1]的线段树;

4:这步很关键了,把排好序的竖边从左到右开始扫描了,如果是矩形的左边那么就插入,要是是矩形的右边了那么就从线段树里删除了!(在这里每次插入和删除线段树要维护好两个重要的值,一个是当前线段树的被覆盖的区间长度的总和,第二个是当前线段树中被覆盖的区间有多少个)!
PS:其中这两句代码非常重要,读者可以画个简单的图进行理解,起始的时候我没明白要记录线段段数的作用,仔细研究了这部分代码发现算线段的段数是为了求得横边的长度,还有一点要注意的是,这棵线段树要建成节点为单元线段的形式,即如果区间为[0,3]
线段树要建成          [0,3]
                         /        \
                      [0,1]    [1,3]
                                   /    \
                               [1,2] [2,3]

这样,我刚开始的时候尝试了一下建成节点的方式,即节点是[1,1] [2,2]这样,结果发现统计区间段数根本没法进行,后来参考过网上的代码发现要这样建树,改了一下就过了,可能平时对这种建树方式还是不太熟悉吧。
参考网址:http://www.cppblog.com/abilitytao/archive/2010/07/21/120927.html

#include<stdio.h>
#include<algorithm>
#include<math.h>
#define N 10005
using namespace std;
int b[N];
struct node
{
    int x,y;
    int cover;//区间被覆盖的次数
    int len;//区间内代表的长度
    int sum;//区间中被覆盖的总长度
    bool lcover,rcover;//标记左右端点是否被覆盖,用于合并区间时统计区间内的离散线段数
    int segnum;//区间内被分成的段数
}a[N*3];
struct line
{
    int st,ed;//竖边的两个y值
    int x;//此条边的x值
    int in;//是否为入边
}bian[N];
bool cmp(line a,line b)
{
    return a.x<b.x;
}
void build(int t,int x,int y)
{
    a[t].x=x;
    a[t].y=y;
    a[t].sum=0;
    a[t].len=b[y]-b[x];
    a[t].cover=0;
    a[t].lcover=a[t].rcover=false;
    a[t].segnum=0;
    if(x+1==y)  return;
    int mid=(x+y)>>1,temp=t<<1;
    build(temp,x,mid);
    build(temp+1,mid,y);
}
void pushdown(int t,int x,int y)
{//求该区间所包含的线段数总量(就是含有不相交的线段的条数),以及区间包含线段的总长度。
    int temp=t<<1;
    if(a[t].cover)
    {
        a[t].sum=a[t].len;
        a[t].lcover=a[t].rcover=true;
        a[t].segnum=1;
    }
    else if(y-x>1)
    {
        a[t].lcover=a[temp].lcover;
        a[t].rcover=a[temp+1].rcover;
        a[t].sum=a[temp].sum+a[temp+1].sum;
        a[t].segnum=a[temp].segnum+a[temp+1].segnum-a[temp].rcover*a[temp+1].lcover;
    }
    else
    {
        a[t].lcover=a[t].rcover=false;
        a[t].sum=0;
        a[t].segnum=0;
    }
}
void update(int t,int x,int y,int flag)
{
    if(a[t].x==x&&a[t].y==y)
    {
        if(flag==1)//插入一条线段
            a[t].cover++;
        else//删除一条线段
            a[t].cover--;
        pushdown(t,x,y);
        return;
    }
    int mid=(a[t].x+a[t].y)>>1,temp=t<<1;
    if(y<=mid)
        update(temp,x,y,flag);
    else if(x>=mid)
        update(temp+1,x,y,flag);
    else
    {
        update(temp,x,mid,flag);
        update(temp+1,mid,y,flag);
    }
    pushdown(t,a[t].x,a[t].y);
}
int main()
{
    int n,i,k,x1,y1,x2,y2,rlen,ans,x,y;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=0;i<n;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            bian[i*2].st=y1;bian[i*2].ed=y2; bian[i*2].x=x1;bian[i*2].in=1;//标记为入边
            bian[i*2+1].st=y1;bian[i*2+1].ed=y2; bian[i*2+1].x=x2; bian[i*2+1].in=0;
            b[i*2]=y1;  b[i*2+1]=y2;
        }
        sort(b,b+n*2);
        sort(bian,bian+n*2,cmp);
        k=1;
        for(i=1;i<n*2;i++)//去重
        {
            if(b[i]!=b[i-1])
                b[k++]=b[i];
        }
        build(1,0,k-1);
        rlen=0;  ans=0;
        for(i=0;i<n*2-1;i++)
        {//lower_bound函数返回一个元素在容器中的迭代器,数组可以看成特殊的容器,所以这里返回的迭代器就是指针
            x=lower_bound(b,b+k,bian[i].st)-b;
            y=lower_bound(b,b+k,bian[i].ed)-b;
            //printf("%d %d\n",x,y);
            if(bian[i].in==1)
                update(1,x,y,1);
            else
                update(1,x,y,0);
            ans+=(bian[i+1].x-bian[i].x)*2*a[1].segnum;
            ans+=abs(a[1].sum-rlen);
            rlen=a[1].sum;
        }
         //特殊处理最后一条出边,因为没有下一条竖边了
        x=lower_bound(b,b+k,bian[i].st)-b;
        y=lower_bound(b,b+k,bian[i].ed)-b;
        update(1,x,y,0);
        ans+=abs(a[1].sum-rlen);
        printf("%d\n",ans);
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值