HDU-1542-Atlantis (线段树 + 扫描线 + 离散化)

https://blog.csdn.net/sugarbliss/article/details/80568257

https://blog.csdn.net/u013480600/article/details/22548393

上面的博客是我为了理解离散化找的两篇较好的博文,两个人在实现上不同。

第一位同志的讲解和代码比较短,我的代码思路是看第一位同志的。两个人在帮助理解离散化和扫描线方面都写得比较好,认真看不浮躁的话,都是很容易理解的——尽管我从无离散化基础,有线段树基础理解这个花了四天,中间还要上课……玩……

但如果要形象理解离散化,看第二位同志的图

但是第一位同志说 "l==r  这是一个点而不是线段" ——在他的pushup函数中

这在我的代码思想中不是这样的,尽管我仿照他的写,结果差不多。但在我看来这是一条最基本的元段,

只不过——见我的下面注释“需要注意的事项.3” + 坐标离散化的思路
/*
需要注意的事项
1.N是输入的区域个数,那么线段树数组长度,点数组长度,边数组长度都要好好考虑
2.只有自己的上边才能消除自己下边的的cnt标记
  如果大段的cnt非零(还没遇到自己的上边) 小段更新不影响大段原来的值
  否则要依靠小段更新
3.my_flush的第二个判断语句else if(l==r) sum[rt] = 0 ;是有必要的
  如果他的cnt标记被消除了,那么他是无法通过下面的sum[rt] =sum[rt<<1] + sum[rt<<1|1];来更新自己的值的
  只能自己置零,本来较小的sum[rt<<1]和sum[rt<<1|1]都是零 是可以通过这个来置零的
  但是如果是较大的N配上较大的rt值(只有最小的不可分割的一段),
  那么sum[rt<<1]和sum[rt<<1|1]会发生数组越界就会出现不可知的值
  如果你将sum开成maxn的32倍就不会出现这种情况——已经试验过了 的确是这个原因 改了就能过
4.if(L<=l&&r<=R)不要写反成if(l<=L&&R<=r) 基本功要扎实
5.unique后的结果是点的个数,减掉一是线段的个数,再减一是从0开始计数的最后一个元段的实际下标
 */
/*坐标离散化的思路
一开始我以为是将每个浮点数点投影成整数点
而实际上这里做的是
将横坐标浮点数组进行升序排序
两个相邻的点构成元段——线段树的最小单位
如何确定一条边对应哪些元段呢?
我们通过lower_bound来确定这条边的起始元段和终止元段
*/

#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define rep(i,a,b) for(ll i=a;i<b;++i)
#define maxn 105
typedef struct ed {
    double l, r, h;
    int f;
} ed;
ed e[maxn << 1];
int cmp(ed a, ed b) {
    return a.h < b.h;
}
double sum[maxn << 3], x[maxn << 1], ans; //N条边 2N个点 最多2*N-1个小段
int    cnt[maxn << 3];
int N, run = 0;
void my_flush(int l, int r, int rt)
{
    if (cnt[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 gx, int l, int r, int rt)
{
    if (l > R || r < L) return ;
    if (L <= l && r <= R)
    {
        cnt[rt] += gx;
        my_flush(l, r, rt);
        return ;
    }
    int m = (r + l) >> 1 ;
    update(L, R, gx, lson); update(L, R, gx, rson);
    my_flush(l, r, rt);
}
int main()
{
    while (~scanf("%d", &N) && N)
    {
        ++run; ans = 0;
        double a, b, c, d;
        rep(i, 0, N)
        {
            scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
            x[2 * i] = a;
            e[2 * i].l = a, e[2 * i].r = c, e[2 * i].h = b; e[2 * i].f = 1;
            x[2 * i + 1] = c;
            e[2 * i + 1].l = a, e[2 * i + 1].r = c, e[2 * i + 1].h = d; e[2 * i + 1].f = -1;
        }
        sort(x, x + 2 * N);
        sort(e, e + 2 * N, cmp);
        /*坐标去重*/
        int ls = unique(x, x + 2 * N) - x - 1 ; //ls 代表有多少个小段 从0开始计数
        memset(sum, 0, sizeof(sum)); memset(cnt, 0, sizeof(cnt));
        double last_h = 0 , now_h = 0;
        rep(i, 0, 2 * N)
        {
            now_h = e[i].h;
            ans += (sum[1] * (now_h - last_h)); last_h = now_h;
            int l = lower_bound(x, x + ls, e[i].l) - x;
            int r = lower_bound(x, x + ls, e[i].r) - x - 1;
            update(l, r, e[i].f, 0, ls - 1, 1);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n", run, ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值