初学线段树总结

线段树善于解决区间最值,和,合并等问题,运用很广泛,下面是一些总结:(自我感觉错误和不足之处有点多啊尴尬

单点更新模版:
#define MAXN 100005
struct node
{
    int l,r,mi,mx,sum;//表示线段[l,r],中的最小、大值,及和值
}tr[MAXN*4];
int a[MAXN];
void build(int id,int l,int r)
{
    tr[id].l=l;
    tr[id].r=r;
    if(l==r)//叶子节点
    {
        tr[id].mx=tr[id].mi=tr[id].sum=a[l];
    }
    else
    {
        int mid=(l+r)/2;
        build(id*2,l,mid);//往左儿子建树
        build(id*2+1,mid+1,r);//往右儿子建树
        //对于编号为id的节点(线段),它左右儿子都建完了
        //接下来就是更新它自身的值,即合并它左右儿子。
        tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
        tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
        tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
    }
}
int quary(int id,int l,int r)//查询[l,r]区间的最小值、最大值、和值
{
    if(l<=tr[id].l&&tr[id].r<=r)
    //如果编号为id的节点(线段)被[l,r]区间覆盖了,则直接返回此线段的结果
    {
        //return tr[id].mi;
        //return tr[id].mx;
        return tr[id].sum;
    }
    else
    {
        int mid=(tr[id].l+tr[id].r)/2;
        if(r<=mid)return quary(id*2,l,r);//r<=mid 说明我们要找的区间在左边
        else if(l>mid)return quary(id*2+1,l,r);//l>mid 我们要找的区间在左边
        else//之间
        {
            int x=quary(id*2,l,r);//先找左边
            int y=quary(id*2+1,l,r);//再找右边
            //最后在合并,返回
            //return min(x,y);
           // return max(x,y);
            return x+y;
        }
    }
}
void update(int id,int pos,int val)//单点更新,把a[pos]修改成val
{
    if(tr[id].l==tr[id].r)
    {
        tr[id].mi=tr[id].mx=tr[id].sum=val;
    }
    else
    {
        int mid=(tr[id].l+tr[id].r)/2;
        if(pos<=mid)update(id*2,pos,val);
        else update(id*2+1,pos,val);
        //同样的道理,回溯的时候有两个儿子节点更新id
        //tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
        //tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
        tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
    }
}
区间更新模版:
#define MAXN 100010
int a[MAXN];
struct node
{
    int l,r,mi,mx;
    long long int sum,sign;//long long 根据实际情况而定
}tr[MAXN*4];
void build(int id,int l,int r)
{
    tr[id].l=l;
    tr[id].r=r;
    tr[id].sign=0;//区间更新初始化
    if(l==r)
        tr[id].sum=a[l];//tr.[id].mi=tr[id].mx=tr[id].sum=a[l]
    else
    {
        int mid=(l+r)/2;
        build(id*2,l,mid);
        build(id*2+1,mid+1,r);
        tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
        //tr[id].mx=max(tr[id*2].mx,tr[id*2+1].mx);
        //tr[id].mi=min(tr[id*2].mi,tr[id*2+1].mi);
    }
}
void add(int id,int l,int r,long long int val)
{
    if(l==tr[id].l&&tr[id].r==r)
        tr[id].sign+=val;//标记要加的值
    else
    {
        //tr[id].ma+=val;
        //tr[id].mi-=val;
        tr[id].sum+=val*(r-l+1);//注意,这一步极其重要,这是找到你要标记区间的前面的大区间的维护,因为以后都是向下更新,所以上面的要提前更新
        int mid=(tr[id].l+tr[id].r)/2;
        if(r<=mid)
            add(id*2,l,r,val);
        else if (l>mid)
            add(id*2+1,l,r,val);
        else
        {
            add(id*2,l,mid,val);
            add(id*2+1,mid+1,r,val);
        }
    }
}
long long int query(int id,int l,int r)//long long 视情况而定
{
    if(l==tr[id].l&&tr[id].r==r)
        return tr[id].sum+(r-l+1)*tr[id].sign;
                //tr[id].ma+=tr[id].sign;
                //tr[id].mi-=tr[id].sign;
    else
    {
        tr[id].sum+=(tr[id].r-tr[id].l+1)*tr[id].sign;//对所到区间都跟新,不管有没有标记,其实也可以判断一下,在看看更新与否
        int mid=(tr[id].l+tr[id].r)/2;
        add(id*2,tr[id].l,mid,tr[id].sign);//向下更新sign标记,若要向下求,就要向下更新
        add(id*2+1,mid+1,tr[id].r,tr[id].sign);
        tr[id].sign=0;
        if(r<=mid)
            return query(id*2,l,r);
        else if(l>mid)
            return query(id*2+1,l,r);
        else
            return query(id*2,l,mid)+query(id*2+1,mid+1,r);
    }
}
以上的这个区间更新版本,跟雨神讲的有点出入,即要一直更新到恰好要标记的地方,上面的大区间值都要更新,而他们是最后查询才更新,再回溯上去
注意这个区间更新版本:(加了lazy,直接在这里面判断是否要向下更新)因为此题查询查询整个区间
void add(int id,int l,int r,int val)
{
    if(l==tr[id].l&&tr[id].r==r)
    {
        tr[id].lazy=1;
        tr[id].sign=val;
        tr[id].sum+=(r-l+1)*val;
    }
    else
    {
        int mid=(tr[id].l+tr[id].r)/2;
        if(tr[id].lazy==1)
        {
            add(id*2,tr[id].l,mid,tr[id].sign);//即这里,上面模版是在query中再往下更新,其实都差不多
            add(id*2+1,mid+1,tr[id].r,tr[id].sign);
            tr[id].sign=0;
            tr[id].lazy=0;
        }
        if(r<=mid)
            add(id*2,l,r,val);
        else if (l>mid)
            add(id*2+1,l,r,val);
        else
        {
            add(id*2,l,mid,val);
            add(id*2+1,mid+1,r,val);
        }
        tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值