POJ 1177 线段树+扫描线

题意:有n个矩形,每个矩形给出它的左下角和右上角的两个点的坐标,要求所有n个矩形在平面中组成的图形的总周长。

分析:用线段树记录坐标系上,与y轴平行的方向覆盖的长度和连续的区间个数。记录矩形的每一条垂直(与y轴平行)的边,按照x从小到大排序。左边的边加入线段树,右边的边移除线段树,计算总周长。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ld d<<1
#define rd d<<1|1
const int N = 5005;
struct seg
{
    int y1,y2,x,flag;              //记录与y轴平行的边,y1、y2为边的两个端端点的y坐标
}a[N << 2];                           //flag == 1表示为左边的边
struct node
{
    int l,r,lbd,rbd,num,len,count;     //线段树的节点,num为区间内有几个不连续的线段,len为该区间内覆盖线段的总长度
}tr[N<<4];                                    //lbd,rbd标记左右端点是否被覆盖
int y[N<<2];
bool cmp(seg a, seg b)
{
    if(a.x == b.x) return a.flag > b.flag;
    return a.x < b.x;
}
void build(int d, int l, int r)
{
        tr[d].l = l,tr[d].r = r;
        tr[d].lbd = tr[d].rbd = tr[d].num = tr[d].len = tr[d].count = 0;
        if(l == r - 1) return;      //因为每个节点都代表一个线段,所以不存在l == r 的情况,此处就应该退出
        int m = (l+r) >>1;
        build(ld,l,m); build(rd,m,r);
}
void update_len(int d, int l, int r)
{
    if(tr[d].count > 0)  tr[d].len = y[r] - y[l];
    else if(r - l ==1) tr[d].len = 0;
    else
    {
        tr[d].len = tr[ld].len + tr[rd].len;
    }
}
void update_num(int d, int l, int r)
{
    if(tr[d].count > 0)
    {
        tr[d].lbd =  tr[d].rbd = tr[d].num = 1;
    }
    else if(r == l + 1) tr[d].lbd = tr[d].rbd = tr[d].num = 0;
    else
    {
        tr[d].lbd = tr[ld].lbd; tr[d].rbd = tr[rd].rbd;
        tr[d].num = tr[ld].num + tr[rd].num - tr[ld].rbd*tr[rd].lbd;
    }
}
void solve(int d, int l, int r, int ql, int qr,int fl)
{
    if(ql <= y[l] && y[r] <= qr)
    {
        tr[d].count += fl;
    }
   else  if(l == r - 1) return;
    else
    {
       int m = (l+r) >> 1;
       if(ql <= y[m]) solve(ld,l,m,ql,qr,fl);      //注意这里的左儿子的区间是(l,m),右儿子的区间是(m,r)因为每个节点代表的是坐标系上的一个线段
       if(y[m] < qr) solve(rd,m,r,ql,qr,fl);
    }
    update_len(d,l,r); update_num(d,l,r);
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
             int x1,x2,y1,y2;
             int k = 0;
             for(int i = 0; i < n; i++)
             {
                      scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
                      a[k].x = x1, a[k].y1 = y1, a[k].y2 = y2, a[k].flag = 1; y[k++] = y1;
                      a[k].x = x2, a[k].y1 = y1, a[k].y2 = y2, a[k].flag = 0; y[k++] = y2;
             }
             sort(a,a+k,cmp);
             sort(y,y+k);
             int cony = unique(y,y+k) - y;  // 将y坐标离散化
            build(1,0,cony - 1);
            int ans = 0,nnum = 0,nlen = 0;
            for(int i = 0; i < k; i++)
            {
                if(a[i].flag)   solve(1,0,cony - 1,a[i].y1,a[i].y2,1);
                else  solve(1,0,cony - 1,a[i].y1,a[i].y2,-1);
                if(i >= 1)
                   ans += 2*nnum*(a[i].x - a[i-1].x);   //计算新增边后与x轴平行的方向增加的周长
                   ans += abs(tr[1].len - nlen);        //与y轴平行方向增加的周长
                nnum = tr[1].num; nlen = tr[1].len;
            }
            printf("%d\n",ans);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值