BZOJ 2957 楼房重建 | 线段树(待补关于lazy)

题意:有n栋楼房,最初高度均为0,建筑队施工共M天。在第i天对第Xi位置的楼房进行操作,将其高度变为Yi。定义建筑物可视:只要存在一个点与(0,0)的连线与先前全部楼房不相交,即为可视。现在让你求每天过后可视的建筑物有多少。

首先需要注意可视的定义,原题的描述是:

如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

这里任何就相当于“存在”……一开始理解的是任意所以搞了半天也没明白样例怎么来的。 读明白题意之后我们画画图,可以发现实际上只要考虑每个线段的最高点就可以了,因为是与(0,0)的连线,所以实质上是看斜率的大小。如果靠后的楼(X大)的斜率比它前面的楼的最大斜率还要大,那么它是可视的,反之则不可视。

最开始所有楼的高度都是0,后面每天对某个位置的楼进行修改,这其实就是一个区间查询问题,所以我们想到可以用线段树来维护它。

我们存储每个区间里最大的斜率maxk和当前节点管辖区间内可视的楼的数目sum,最初建树的时候maxk,sum均为0。后面通过每次update对对应节点的maxk进行修改,通过Upgrade()更新。怎么维护sum呢?

这里我觉得可以通过类似归并(分块?)的思想来理解,如果我们分成只有1的许多区间,那么,对于每个区间肯定自身都是可视的(因为只有自己这一栋楼,所以肯定不会被遮挡)。

之后不重合地(加粗是因为我发现我对线段树结构的理解一直有问题)两两合并,那么,现在每个区间里有两栋楼,我们只要比较他们maxk的大小,如果靠后(也就是靠右)的楼maxk小于或等于靠前(也就是左子叶)的楼,那么一定不可视;反之可视,当前区间可视的数目就是2 。

这样一层层推上去,在我们进行upgrade(int id, int num, int val)操作的时候,最初我们设置的upgrade(1, num, val)从根节点找,通过num与mid=(l+r)/2的相对关系确定向左还是向右递归,最后,缩到叶子节点时return,再一层层return回去。因为我们在upgrade最后写了push_up(i),所以这样就相当于从叶子节点一层层更新上去回到根节点!(这里我理解的一直不对,总觉得每次query需要再来一个push_up(1))

关于这里为什么不用lazy,什么时候需要lazy我对线段树理解的还不够透彻,所以只好留坑以后再补啦。

最后说一下关于线段树的结构。像图1.2表示的,题目中给出的数据其实就是最下面一行的黑色圈圈,之后我们不重合地两两合并直到最后只有一个圈圈,也就是到根了。线段树的标号如绿色笔所示,旁边标注的是所管辖的区间。所以我们遇到一些要与原本黑色数组对应的赋值的时候会优先取.l。
在这里插入图片描述
再最后……发现我读题还是有点问题。题目中说明了:

对于所有的数据
1<=Xi<=N 1<=Yi<=10^9 N,M<=100000

也就是说每个Xi是不会超过N的,对拍的时候生成器这里设置的有问题,所以一直在往别的方向找错。最后发现一直WA是因为update()函数参数表里面val没有设置为double,一直写的都是Int……改过来就AC啦。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
//const int maxn = 100005;//题目描述n

struct node
{
    int l, r, sum;//节点表示范围l~r
//sum节点属性,lazy跟新延迟标记
    double maxk;
}tr[400040];

int get(int id, double k)
{
    if(tr[id].l == tr[id].r)
    {
        if(tr[id].maxk <= k)
            return 0;
        else
            return 1;
    }

    if(tr[id<<1].maxk > k)
        return tr[id].sum - tr[id<<1].sum + get(id<<1, k);
    else
        return get(id<<1|1, k);

}


//回溯跟新
void push_up(int id)
{
    tr[id].maxk = max(tr[id<<1].maxk, tr[id<<1|1].maxk);
    tr[id].sum = tr[id<<1].sum + get(id<<1|1, tr[id<<1].maxk);
    /*tr[id].sum = tr[id<<1].sum;
    if(tr[id<<1|1].maxk > tr[id<<1].maxk)
        tr[id].sum = tr[id<<1].sum + get(id<<1|1, tr[id<<1].maxk);*/
}

//建立线段树
void build(int id, int l, int r)
{
    tr[id].l=l;
    tr[id].r=r;
    //tr[id].sum=tr[id].maxk=0;

    //printf("k = %d l = %d r = %d\n",id,l,r);

    if(l == r)
        return;

    int mid=(l+r)>>1;
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);

    //push_up(id);//建立的过程中顺带区间统计
}


//更新线段树
void update(int id, int num,double val)
{
    //printf("id%d num%lld l%d r%d \n", id, num, tr[id].l, tr[id].r);
    if(tr[id].l==tr[id].r)
    {
        tr[id].maxk = val;
        if(val > 0)
            tr[id].sum = 1;
        else
            tr[id].sum = 0;
        tr[id].sum = 1;
        return;
    }

    int mid=(tr[id].l+tr[id].r)>>1;
    if(num<=mid)
        update(id<<1, num, val);
    else
        update(id<<1|1, num, val);

    push_up(id);
}


int main()
{
    int n,q;
    scanf("%d %d",&n,&q);

    build(1,1,n);

    for(int i=1;i<=q;i++)
    {
        int num, val;
        scanf("%d %d",&num, &val);
        update(1,num,(double)val/num);
        printf("%d\n",tr[1].sum);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值