HDU 1542 Atlantis(线段树+扫描线)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542

解题思路:


Part 1  扫描线相关介绍:

简而言之,按顺序扫过这些个正方形的竖边,然后处理当前竖边在y轴上的投影

每一个矩形都有两条边,入边就对区间覆盖一次,出边就消去一次。(不是全部清除)

如图,两个有重叠的矩形的面积就分成了三部分算。

每次只要算  两条竖边的间距×在y轴投影就OK


Part 2 线段树的构建

线段树维护当前扫过线段在y轴上的投影长度。

由于线段树维护的区间时整形的,所以需要对y的坐标离散化。

本题的难点是线段树区间覆盖要记录次数。

普通的区间覆盖就是区间染色,可以pushup,pushdown实现。

而这题的问题在于区间清0操作你无法判断当前是否应该清空区间数据,因为可能区间被覆盖了多层。

这道题,利用一个标记记录区间被染色的次数用于辅助线段树的建立(不是下放标记),如果标记不为0,证明整段区间都被染色。

区间为0,就从两个儿子传过来,来保证当前节点整个区间没有染色时可以从子节点得到信息。


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define debug(x) printf("----Line%s----\n",#x)
#define ll long long
#define mid int m=l+r>>1
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define tl tree[rt<<1]
#define tr tree[rt<<1|1]
using namespace std;

const int N = 100*2+5;

struct T
{
    double x;
    double l,r;
    bool ru;
    bool operator < (T a)const {
        return x < a.x;
    }
}edge[N];

double date[N];

double tree[N<<2];
int lazy[N<<2];
map<double,int>mp;

void push_up(int rt,int l,int r)
{
    if (lazy[rt]) tree[rt] = date[r] - date[l-1];
    else if (l==r) tree[rt] = 0;
    else tree[rt] = tl+tr;
}

void update(int L,int R,int c,int rt,int l,int r)
    if (L<=l && r<=R){
        lazy[rt] += c;
        push_up(rt,l,r);
        //printf("tree[%d]=%.2f\n",rt,tree[rt]);
        return ;
    }
    mid;
    if (L<=m) update(L,R,c,lson);
    if (R>m)  update(L,R,c,rson);
    push_up(rt,l,r);//printf("tree[%d]=%.2f\n",rt,tree[rt]);
}

int main()
{
    int n,kca=1;
    while (~scanf("%d",&n),n){
        memset(lazy,0,sizeof lazy);
        memset(tree,0,sizeof tree);
        double x1,y1,x2,y2;
        int tot = 0;
        for (int i=1;i<=n;i++){
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            edge[tot].x = x1;
            edge[tot].l = y1;
            edge[tot].r = y2;
            edge[tot].ru = true;  date[tot] = y1;
            edge[++tot].x = x2;
            edge[tot].l = y1;
            edge[tot].r = y2;
            edge[tot].ru = false; date[tot++] = y2;
        }
        sort(date,date+tot);
        int cnt = unique(date,date+tot)-date;

        for (int i=0;i<cnt;i++) mp[date[i]] = i;

        sort(edge,edge+tot);

        double area = 0;
        for (int i=0;i<tot-1;i++){

            update(mp[edge[i].l]+1,mp[edge[i].r],edge[i].ru?1:-1,1,1,cnt-1);

            double xx = edge[i+1].x-edge[i].x;
            double yy = tree[1];
            area += xx*yy;
            //printf("xx=%.2f,yy=%.2f,xx*yy=%.2f\n",xx,yy,xx*yy);

        }
        printf("Test case #%d\n",kca++);
        printf("Total explored area: %.2f\n\n",area);
    }
    return 0;
}

总结:

区间多次覆盖的线段树用到一个跟之前都不同的标记,用于记录当前节点对应区间被覆盖次数,且这个信息只是针对某一个节点,不会向上向下传递,通过它来辅助线段树维护正确的信息。

吐槽:

啊,已经做了好长一段时间线段树了,可恶,kuangbin的线段树专题还没写完,下次再做线段树估计的好久之后了,下个礼拜弄弄各种字符串的数据结构。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值