HDU 1542 Atlantis (线段树+扫描线拆分理解 超详细)

之前没有做过这类题,只能模仿着学习,现在扫描线的作用和原理差不多明白了,实现方法各位大牛各有见解,我找一种比较容易实现的思路清晰代码学习


推荐一个好的博客先,先看懂原理再说:
链接: 转载大牛的

这个兄弟的图画的很好,看看他画的图就行:看图

完整代码:

#include <cstdio>        
#include <cstring>        
#include <algorithm>        
#include <vector>        
#include <queue>        
using namespace std;        
typedef long long LL;        
#define lson rt << 1, l, mid        
#define rson rt << 1|1, mid + 1, r        
const int MAXN = 2000 + 5;        
int  Col[MAXN << 2], n, cnt, res;        
double X[MAXN << 2], Sum[MAXN << 2];        
struct seg {        
    double l,r,h;        
    int s;        
    seg() {}        
    seg(double l,double r,double h,int s):l(l),r(r),h(h),s(s) {}        
    bool operator < (const seg & object) const {        
        return h < object.h;        
    }        
} S[MAXN];        
        
        
void pushup(int rt,int l,int r) {        
    if (Col[rt]) Sum[rt] = X[r+1] - X[l];//利用[ , ),这个区间性质,左闭右开        
    else if (l == r) Sum[rt] = 0;        
    else Sum[rt] = Sum[rt<<1] + Sum[rt<<1|1];        
}        
        
void update(int L, int R, int c,int rt,int l, int r) {        
    if(L <= l && r <= R) {        
        Col[rt] += c;        
        pushup(rt,l,r);        
        return ;        
    }        
    int mid = (l + r) >> 1;        
    if(L <= mid) update(L, R, c, lson);        
    if(R > mid) update(L, R, c, rson);        
    pushup(rt,l,r);        
}        
        
int binary_find(double x){        
    int lb = -1,ub = res - 1;        
    while(ub - lb > 1){        
        int mid = (lb + ub) >> 1;        
        if(X[mid] >= x) ub = mid;        
        else lb = mid;        
    }        
    return ub;        
}        
        
int main() {        
    int cas = 1;        
    while(~ scanf("%d", &n), n) {        
        cnt = res = 0;        
        for(int i = 0 ; i < n; i ++) {        
            double a,b,c,d;        
            scanf("%lf%lf%lf%lf",&a, &b, &c,&d);        
            S[cnt] = seg(a, c, b, 1);        
            X[cnt ++] = a;        
            S[cnt] = seg(a, c, d, -1);        
            X[cnt ++] = c;        
        }        
        sort(X, X + cnt);        
        sort(S, S + cnt);        
        res ++;        
        for(int i = 1; i < cnt; i ++) {        
            if(X[i] != X[i - 1]) X[res ++] = X[i];        
        }        
        
        memset(Sum, 0, sizeof(Sum));        
        memset(Col, 0, sizeof(Col));        
        double ans = 0;        
        for(int i = 0;i < cnt - 1;i ++){        
            int l = binary_find(S[i].l);        
            int r = binary_find(S[i].r) - 1;//利用[ , ),这个区间性质,左闭右开        
            update(l, r, S[i].s, 1, 0, res - 1);        
            ans += Sum[1] * (S[i + 1].h - S[i].h);        
        }        
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ans);        
    }        
    return 0;        
}        

模板出处:。。。

拆分理解:
首先知道离散化
所谓的离散化,大家可以简单的理解为,将一组很大的数据,
浓缩为一组很小的数据,用这组数据来代替原数据的作用,
比如给你1000个数,数的范围为(1,1e18)我们这里就可以用离散化,
由于只有1000个数,我们可以用一个数组的下标代表提供的每一数,
如果需要这个数据了,由于是下标,可以直接通过下标获得,如此就是离散化


for(int i = 0 ; i < n; i ++) {  //输入数据并进行存储      
            double a,b,c,d;       
            scanf("%lf%lf%lf%lf",&a, &b, &c,&d);        
            S[cnt] = seg(a, c, b, 1);  //存矩形下边      
            X[cnt ++] = a;  //存储所有的x坐标以便进行离散化处理      
            S[cnt] = seg(a, c, d, -1);  //存矩形上边      
            X[cnt ++] = c;        
        }
sort(X, X + cnt);  //横坐标按从小到大排列好      
sort(S, S + cnt);  //矩形按照纵坐标从小到大排列好

for(int i = 1; i < cnt; i ++) { //对横坐标去重,去除无用的数据,便于后面进行处理    
           if(X[i] != X[i - 1]) X[res ++] = X[i];     
}

struct seg {  //用这个结构体存矩形的信息    
    double l,r,h;      
    int s;      
    seg() {}      
    seg(double l,double r,double h,int s):l(l),r(r),h(h),s(s) {}      
    bool operator < (const seg & object) const {      
        return h < object.h;      
    }      
} S[MAXN];

int binary_find(double x){  //二分查找,效率高,快速找到所需横坐标对应的数组下标    
    int lb = -1,ub = res - 1;      
    while(ub - lb > 1){      
        int mid = (lb + ub) >> 1;      
        if(X[mid] >= x) ub = mid;      
        else lb = mid;      
    }      
    return ub;      
}

void pushup(int rt,int l,int r) {
        if (Col[rt])   //进行标记,如果标记大于0说明是可以进行计算
        Sum[rt] = X[r+1] - X[l];// 计算有效长度,关于为什么时r+1而不是r,后面会提到   
        else if (l == r) Sum[rt] = 0;//倘若遍历到了最后一个点还没找到,就说明这个区间不符合条件,有效长度为0
        else Sum[rt] = Sum[rt<<1] + Sum[rt<<1|1]; //相当于建树,在找到有效区间后停止建树,通过递归将有效的长度更新
}


void update(int L, int R, int c,int rt,int l, int r) { //此处的L,R,l,r都是离散化后存储在数组里的横坐标对应的下标  
        if(L <= l && r <= R) { //如果当前区间包含了目标区间 ,递归结束
        Col[rt] += c; //加上标记(下边为1,上边为-1)  
        pushup(rt,l,r);//此时已经进入了符合条件的区间
        return ;  //更新完sum[1]即有效长度就可以溜了
            }  
        int mid = (l + r) >> 1; //当前区间不包含目标区间就继续向下找  
        if(L <= mid) update(L, R, c, lson);  
        if(R > mid) update(L, R, c, rson); pushup(rt,l,r);  
}

/*一般的线段树以及我们的区间修改合并,都有一个共同点,就是不会出现区间缺失的现象,什么叫区间缺失,顾名思义,区间缺失就是缺少一些区间没有进行运算,这里的扫描线就会遇到这个问题。普遍的,我们的线段树以及数据区间分布是这样的:[1, a][a + 1, b][b + 1, c][c + 1, d][d + 1, e].......但是如果只是简简单单的用这个来解决扫描线的问题会导致错误,为什么因为,他没有涉及到[a,a + 1],在扫描线中会出现[a,a + 1]中的数据,而常用的线段树的区间概念是无法解决这样的问题的,出现了所谓的区间缺失,怎样解决,下面的代码给出了解决方案,这里简单的提一下,就是利用[ , ),这个区间性质,左闭右开,即可解决区间缺失问题*/

for(int i = 0;i < cnt - 1;i ++){   
 int l = binary_find(S[i].l); //寻找每一个小区间左右端点在数组里对应的下标  
 int r = binary_find(S[i].r) - 1;//注意这里的r有一个减一操作,这就是上面r+1的原因,保证区间左闭右开,这样可以防止区间缺失 
 update(l, r, S[i].s, 1, 0, res - 1); //更新每一条边  
 ans += Sum[1] * (S[i + 1].h - S[i].h); //求出结果,其中(S[i + 1].h - S[i].h)是当前情况下的高,sum[1]是底(需要搞清原理)  
 //为什么底是sum[1],高是(S[i + 1].h - S[i].h)呢  
 //因为sum[1]是线段树的顶点,因为它是从[0,res-1]的最完整的那条线段,我们不停地修改这条线段的标记以达到修改矩形的底的目的,并且依次向上寻找y坐标作为矩形的高
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ans);  //最后输出就ok了



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值