POJ - 1177 Picture 扫描线求周长

POJ - 1177 Picture

详解:算法总结:【线段树+扫描线】&矩形覆盖求面积/周长问题(HDU 1542/HDU 1828)

参考了大佬的思路

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ls rt<<1
#define rs rt<<1|1
#define mid() (p[rt].l+p[rt].r)>>1
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5010;
struct edge
{
    int l,r;   //左右端点
    int h;     //高
    int v;     //是上边还是下边
}e[maxn<<2];
bool cmp(edge a,edge b)
{
    return a.h<b.h;
}
struct node
{
    int l,r;      //该节点代表的线段的左右端点坐标
    int len;      //这个区间被覆盖的长度
    int s;        //表示这个区间被重复覆盖了几次
    bool lc,rc;   //表示这个节点左右两个端点是否被覆盖(0表示没有被覆盖,1表示有被覆盖)
    int num;      //这个区间有多少条线段(这个区间被多少条线段覆盖)
      //len用来计算横线 num用来计算竖线
}p[maxn<<4];
void build(int l,int r,int rt)
{
    p[rt].l = l , p[rt].r = r;
    p[rt].lc = p[rt].rc = 0;
    p[rt].num = p[rt].len = p[rt].s = 0;
    if(l==r)
        return ;
    int m=mid();
    build(l,m,ls);
    build(m+1,r,rs);
}
void pushup(int rt)    //区间合并
{
    if(p[rt].s)     //完全覆盖
    {
        p[rt].len = p[rt].r-p[rt].l+1;
        p[rt].num = 1;
        p[rt].lc = p[rt].rc = 1;
    }
    else if(p[rt].l==p[rt].r)   //是一个点
    {
        p[rt].len = 0;
        p[rt].num = 0;
        p[rt].lc = p[rt].rc = 0;
    }
    else                    //根据左右子节点更新这个点        
    {
        p[rt].len = p[ls].len+p[rs].len;
        //和左儿子共左端点,和右儿子共右端点
        p[rt].lc = p[ls].lc; 
        p[rt].rc = p[rs].rc;
        p[rt].num = p[ls].num+p[rs].num-(p[ls].rc&p[rs].lc);   //如果左子的右端点和右子的左端点都被覆盖了
        
    }
}
void update(int l,int r,int rt,int f)
{
    if(p[rt].l==l && p[rt].r==r)
    {
        p[rt].s += f;
        pushup(rt);
        return ;
    }
    int m = mid();
    if(l>m)
        update(l,r,rs,f);
    else if(r<=m)
        update(l,r,ls,f);
    else
    {
        update(l,m,ls,f);
        update(m+1,r,rs,f);
    }
    pushup(rt);
}
int main()
{
    int n;
    int tot=0;
    int x1,y1,x2,y2;
    int mn=inf;
    int mx=-inf;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        mn=min(mn,min(x1,x2));
        mx=max(mx,max(x1,x2));
        e[tot].l = e[tot+1].l = x1;
        e[tot].r = e[tot+1].r = x2;
        e[tot].h = y1;
        e[tot+1].h = y2;
        e[tot].v = 1 , e[tot+1].v = -1;
        tot += 2;
    }
    sort(e,e+tot,cmp);
    int ans=0;       //周长
    int last=0;      //保存上一次的总区间的被覆盖的长度
    build(mn,mx,1);      
     //每两条横线之间才会有竖线
    for(int i=0;i<tot;i++)
    {
        update(e[i].l,e[i].r-1,1,e[i].v);
     //横线:现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值
        ans += abs(p[1].len-last);   
     //竖线:[下一条横线的高度-现在这条横线的高度]*2*num   
        ans += (e[i+1].h-e[i].h)*2*p[1].num;
        last = p[1].len;      //每次都要更新上一次总区间覆盖的长度
    }
    printf("%d\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值