HDU 1255 覆盖的面积 扫描线+线段树

在这道题之前做过一次扫描线,这次首先就往扫描线上想了。最开始的想法非常傻,就是记录当前坐标长度范围内被覆盖的层数,以及最近一次被覆盖层数由1变成2的位置。每次覆盖的层数要减一了,就去查询,更新什么的。啊呀,太傻了!写着写着就写不下去了,本想把这一发代码贴出来教大家看了笑一笑,结果又弄丢了。

其实我们应该这样,对于每一个线段树上的区间,我们去记录他的纵坐标范围,平行线的序号范围,覆盖两次以上的长度,覆盖一次以上的长度,还有覆盖的次数。这个覆盖的次数就很有讲究,update的时候,我们只在更新的区间和当前区间一模一样的时候才去动它,这么一来后面计算长度的操作,就要这样:当覆盖的数大于等于2的时候,说明这个区间真的全都被覆盖了两次及以上,所以记录覆盖长度的那两个量就都等于区间的长度。当覆盖的数等于一的时候,覆盖超过一次的长度没什么悬念,就是区间的长度。覆盖超过两次的长度,就得看它两个子区间被覆盖了超过一次的长度,其实就是这两者的和。为什么这么做,写题的时候还纳闷了好一会儿,道理是这样的,覆盖数得是把这个区间完全覆盖了的矩形才能计进去的,而今做这一步运算的时候,肯定要么正在计数的线段没有把整个线段树的区间都覆盖,要么它两个子区间还没更新。所以计算覆盖了两次及以上的长度,自然就是子区间覆盖了一次的长度之和。我们也不用担心这么一来会在以后的查询中查询到相当于懒标记尚未到达处的子区间,因为没有更新过的子区间,查询的时候那也更用不到哇!

然后,如果这一次发现区间的覆盖数是0,那么如果这个区间对应的纵坐标范围只是1,那就没什么好说的了,反正又没有子区间,什么覆盖数都是0。否则的话,就只能老老实实继承子区间对应数值之和了。

再整个离散化,就挺好的了。关于一开始那个记录最近一次被覆盖层数由1变成2的位置的蠢想法,就是没有充分理解扫描线的妙处,只要一个竖线一个竖线地扫下来,扫一次加一次面积,就能把答案算出来了。

 for (int i=2; i<cnt; i++)
        {
            //9cout<<"i="<<i<<endl;
            ans+=tree[1].sum*(line[i].x-line[i-1].x);
            tmp.update(1,line[i]);
        }
struct segment_tree{
    void build(int l,int r,int rt)
    {
        tree[rt].l=l;
        tree[rt].r=r;
        tree[rt].sum=0;
        tree[rt].one=0;
        tree[rt].lf=y[l];
        tree[rt].rf=y[r];
        if (l+1==r)
        {
            return;
        }
        int mid=l+r>>1;
        build(l,mid,rt*2);
        build(mid,r,rt*2+1);
    }
    void update(int rt,Line e)
    {
        if (tree[rt].lf==e.d && tree[rt].rf==e.u)
        {
            tree[rt].c+=e.flag;
            query(rt);
            return;
        }
        if (e.u<=tree[rt*2].rf) update(rt*2,e);
        else if (e.d>=tree[rt*2+1].lf) update(rt*2+1,e);
        else
        {
            Line tmp=e;
            tmp.u=tree[rt*2].rf;
            update(rt*2,tmp);
            tmp=e;
            tmp.d=tree[rt*2+1].lf;
            update(rt*2+1,tmp);
        }
        query(rt);
    }
    void query(int rt)
    {
        if (tree[rt].c>=2)
        {
            tree[rt].sum=tree[rt].rf-tree[rt].lf;
            tree[rt].one=tree[rt].rf-tree[rt].lf;
            return;
        }
        else if (tree[rt].c==1)
        {
            tree[rt].one=tree[rt].rf-tree[rt].lf;
            //cout<<"rtl="<<tree[rt*2].lf<<" rtr="<<tree[rt*2].rf<<" one="<<tree[rt*2].one<<endl;
            //cout<<"2rtl="<<tree[rt*2+1].lf<<" 2rtr="<<tree[rt*2+1].rf<<" one="<<tree[rt*2+1].one<<endl;
            if (tree[rt].l+1==tree[rt].r) tree[rt].sum=0;
            else tree[rt].sum=tree[rt*2].one+tree[rt*2+1].one;
            //cout<<"rtl="<<tree[rt].lf<<" rtr="<<tree[rt].rf<<" sum="<<tree[rt].sum<<endl;
        }else
        {
            if (tree[rt].l+1==tree[rt].r)
            {
                tree[rt].one=0; tree[rt].sum=0;
            }else
            {
                tree[rt].one=tree[rt*2].one+tree[rt*2+1].one;
                tree[rt].sum=tree[rt*2].sum+tree[rt*2+1].sum;
            }
        }
    }
};

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值