SKYLINE UVa 1232 线段树区间修改

        题目大意是,每个建筑有左右端点和高度这三个数值,按顺序计算建筑的轮廓线长度。如果当前的建筑,在某一区间内的轮廓线高度比其之前所有建筑的轮廓线高度要高,那么这一区间长度是它的轮廓线长度的一部分。

        第一次学习线段树区间修改,没有拿到模板题而是直接拿到了变式,就当做自己的模板题了。学习了队长的写法,模板和找到的一些题解都不一样,但我觉得很好理解,结构易懂,但是就不知道如何转化成经典的模板,有空再扩展学习。

        首先说明区间修改中,延迟更新的概念。在区间节点t[i]上,要对其区间上所有点进行更新,那么把这些更新积累在t[i]上,先不对它的子结点进行更新以节约时间。比如区间[1,16],如果有三个建筑高度依次是1,2,3,那么依次更新所有结点需要几十次操作,而把这些更新累计到t[i]这个刚好覆盖区间的结点上来,就只需要三次操作。当进行更新有必要访问它的子区间时,再把这个更新操作给子区间,也只进行了一次高度更新的操作,通过这样的方法来节约时间。

        然后说明pushup pushdown这两个函数的作用,对于x号节点,它的子结点被更新过之后需要pushup来得到新的x号节点高度;当新的修改需要访问子区间的数值时,用pushdown把停滞在x号节点的操作传递下去。

        代码中有注释。

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=(100001);
struct node
{
    int l,r,lazy;
}t[maxn*3];//不知道这里为什么要开三倍的数组,据说四倍才稳妥,但是理论上最多2*i个节点吧?先记下需要开3或4倍数组这个结论
int sum;
int left,Right,height,l[maxn],r[maxn],h[maxn];
void build(int i,int l,int r)
{
    if(l==r)
    {
        t[i].l=t[i].r=l;
        t[i].lazy=0;//这里lazy的功能集是否有操作停滞在这个节点,和这个区间的高度是多少,两项功能为一体,如果不是-1,存的就是轮廓线高度,-1就代表没有向下的操作停滞在这个节点
        return ;
    }

    else
    {
        t[i].l=l;
        t[i].r=r;
        t[i].lazy=0;
        int mid=(l+r)>>1;
        build(i<<1,l,mid);
        build(i<<1|1,mid+1,r);
    }
}
void pushup(int x)
{
    if(t[x].l==t[x].r) return ;
    if(t[x<<1].lazy==t[x<<1|1].lazy)
        t[x].lazy=t[x<<1].lazy;
    else t[x].lazy=-1;//-1表示该区间并不是同高度区间,即更新已经进行
}
void pushdown(int x)
{
    if(t[x].l==t[x].r||t[x].lazy==-1) return ;
    t[x<<1].lazy=t[x<<1|1].lazy=t[x].lazy;
    t[x].lazy=-1;//往下更新了以后就要重置lazy为-1了,否则问询到的时候会出错
}
int getlength(int x,int l,int r)//得到相应区间中,轮廓线的长度。如果这个区间lazy!=-1,含义是区间上轮廓线的高度均为lazy
{
    if(t[x].lazy>height) return 0;//如果区间上的高度均为lazy,且大于height,长度即为0
    if(t[x].lazy!=-1)
    {
        return r-l+1;//lazy不是-1说明有操作停滞在x节点对应的区间上没有向下更新,在这里也是意味着这个区间上的所有建筑轮廓线最高高度均为lazy
    }
    int mid=(t[x].l+t[x].r)>>1;//这就是lazy==-1的情况,没有操作停滞在当前节点上,需要向下寻找合适的区间来计算长度
    int length;
    if(r<=mid) length=getlength(x<<1,l,r);
    else if(l>mid) length=getlength(x<<1|1,l,r);
    else length=getlength(x<<1,l,mid)+getlength(x<<1|1,mid+1,r);
    return length;
}
void update(int x,int l,int r)
{
    if(t[x].lazy>height) return ;
    if(t[x].lazy!=-1&&l==t[x].l&&r==t[x].r)//区间长度刚好对应节点的区间长度时,就可以把lazy值直接放进来
    {
        t[x].lazy=height;
        return ;
    }
//    printf("[%d,%d]   %d -> %d\n",t[x].l,t[x].r,t[x].lazy,height);
    pushdown(x);//并不是节点对应的区间时,要把这个节点停滞的操作进行下去。当然这里pushdown中有判断是否lazy==-1的语句,把这个语句放到这里来也是可以的
    int mid=(t[x].l+t[x].r)>>1;
    if(r<=mid) update(x<<1,l,r);
    else if(l>mid) update(x<<1|1,l,r);
    else
    {
        update(x<<1,l,mid);
        update(x<<1|1,mid+1,r);
    }
    pushup(x);
}
int main()
{
    int c;
    scanf("%d",&c);
    while(c--)
    {
        int n;
        scanf("%d",&n);
        sum=0;
        Right=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d%d",&l[i],&r[i],&h[i]);
            if(r[i]>Right) Right=r[i];
        }
        build(1,1,Right-1);
        for(int i=1;i<=n;i++)
        {
            height=h[i];
            sum+=getlength(1,l[i],r[i]-1);
//            cout<<sum<<' ';
            update(1,l[i],r[i]-1);//这道题非常关键的地方就是,要把区间理解成,第l个区间到第r个区间,而不能理解成点,因为轮廓线只有放在区间上才有意义,放在同一个点上可以有两个高度的值,优先判断高的就导致结果偏小
        }
        printf("%d\n",sum);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xuzonghao/article/details/80861707