线段树总结与模板

线段树也是一种高级数据结垢,经典的log型。

树状数组能做的事它都能做(单点修改区间求和与区间修改单点查询),它能做的事树状数组不一定能做(区间修改区间查询或查询最大值)。

这里有俩例题,代码风格也不太一样,大家将就一点。

这道题需要单点修改区间查询最大值,用树状数组事维护不了的,只好上一个线段树。

build一个结构体存储的树,每个节点控制一段原数组的区间。刚开始什么都没有,我们把它的左右节点弄好。

build(1,1,n);
void build(int now,int zuo,int you)
{
    o[now].zuo=zuo;o[now].you=you;
    if(zuo==you)return ;
    int mid=(zuo+you)/2;
    build(now*2,zuo,mid);
    build(now*2+1,mid+1,you);
}

对于每个修改操作,就直接冲到最里面把对应的叶子节点改掉,然后更新自己的max。

cin>>tx>>tv;
change(1);
void change(int now)
{
    if(o[now].zuo==o[now].you)
    {
        o[now].maxx=tv;
        return ;
    }
    int mid=(o[now].zuo+o[now].you)/2;
    if(tx<=mid)change(now*2);
    else       change(now*2+1);
    o[now].maxx=max(o[now*2].maxx,o[now*2+1].maxx);
    return ;
}

对于每个询问操作,如果当前节点管的区间被询问的区间包含,那么可以直接输出当前的max。否则我们要去看左右节点关于这个区间怎么说。

cin>>tz>>ty;//t左,t右
cout<<ask(1)<<endl;
int ask(int now)
{
    if(tz<=o[now].zuo&&o[now].you<=ty)
        return o[now].maxx;
    int mid=(o[now].zuo+o[now].you)/2,ans=0;
    if(tz<=mid)ans=ask(now*2);
    if(ty>mid) ans=max(ans,ask(now*2+1));
    return ans;
}

本题是一道区间修改区间查询。这个就要难很多了,我们需要一个懒标记add[],每次修改的时候有时可以只改懒标记就不更改子节点,每次查询的时候再带着懒标记;

具体实现过程(以区间最大值举例):每次修改或查询完后的maxx等上面的add传下来后一定是正确的.这句话非常重要.因为有这个性质,每次修改时如果修改的区间完全覆盖当前节点负责的区间可以直接把maxx+=,add+=,每次查询时我们如果完全覆盖可以直接returnmaxx.为了维护这个性质,如果没有完全覆盖就需要先pushdown,把儿子的maxx+=,add+=.确保了儿子的性质后自己的add就可以归零,然后去查询两个儿子.

做这道题的时候突然发现可以不用结构体,函数自带l,r也行,只不过三个参数看起来很难受(玩的花),那么这样子就用不到build建树了。


对于每个change,输入tr,tl作为全局变量,然后调用函数change(1,1,n),三个参数是now,l,r。now表示当前处于下标为now的节点上,管的是区间l到r的数。那么如果这个节点管的区间在tr,tl之间那么令max[now]++,add[now]++,ruturn即可。这时子节点们都还没有更新(重要!)
如果不是全部覆盖,就需要二分当前区间把需要改的改掉。
先把自己的add传到下面,pushdown一次,确保子节点的正确性。

改完子节点后回来更新自己的max[now]=max(max[lc],max[rc]);在这些操作中可以看到如果时叶子节点即l[now]==r[now]的时一定直接return的,不会参与pushdown。而且每次带进去的参数l,r已经告诉我now管控的范围了,就不需要写结构体。

cin>>tl>>tr;
change(1,1,n);

void pushdown(int now)
{
    if(add[now])
    {
        add[now<<1]+=add[now];
        add[(now<<1)+1]+=add[now];
        maxx[now<<1]+=add[now];
        maxx[(now<<1)+1]+=add[now];
        add[now]=0;
    }
}


void change(int now,int l,int r)
{
    if(tl<=l&&r<=tr)
    {
        maxx[now]++;
        add[now]++;
        return;
    }
    int mid=(l+r)/2;
    int lc=now<<1;
    int rc=(now<<1)+1;
    pushdown(now);
    if(tl<=mid)
        change(lc,l,mid);
    if(tr>mid)
        change(rc,mid+1,r);
    maxx[now]=max(maxx[lc],maxx[rc]);
}


对于每次询问
依旧全局变量tr,tl,cout<<ask(1,1,n)。
如果tl<=l&&r<=tr可以直接return max[now];因为既然来了,就确保max已经被更新过了
否则pushdown,询问下面的子节点并且return ;

cin>>tr>>tl;
cout<<ask(1,1,n);
int ask(int now,int l,int r)
{
    if(tl<=l&&r<=tr)
        return maxx[now];
    int mid=(l+r)/2;
    int lc=now<<1;
    int rc=(now<<1)+1;
    pushdown(now);
    int ans=0;
    if(tl<=mid)
        ans=ask(lc,l,mid);
    if(tr>mid)
        ans=max(ans,ask(rc,mid+1,r));
    return ans;
}

 

转载于:https://www.cnblogs.com/qywyt/p/9618984.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值