线段树 lazy

  线段树的写法,主要就分三大部分,也就是三个函数:建树(build)、操作(update),以及询问(query)。

  此代码为区间修改,区间查询。

  首先建树:

void build(int L,int R,int now)
{
    l[now]=L;
    r[now]=R;
    //lazy[now]=0;
    if(L==R)
    {
        scanf("%lld",&sum[now]);
        return ;
    }
    int mid=(L+R)/2;
    build(L,mid,now*2);
    build(mid+1,R,now*2+1);
    sum[now]=sum[now*2]+sum[now*2+1];
}

  每一次将已有区间分为两部分,直到区间中剩下一个元素(为研究方便,假装存在这样的区间)。对于判断,只要(L==R)成立即可,此时便直接赋值,并返回。(l[]为now编号的区间的最左端的元素值,r[]为最右端元素值)

  那么对于修改:

void update(int L,int R,ll d,int now)
{
    if(l[now]==L&&r[now]==R)
    {
        lazy[now]+=d;
        return ;
    }
    sum[now]+=(long long)d*(R-L+1);
    int mid=(l[now]+r[now])/2;
    if(R<=mid)
        update(L,R,d,now*2);
    else if(mid<L)
        update(L,R,d,now*2+1);
    else
    {
        update(L,mid,d,now*2);
        update(mid+1,R,d,now*2+1);
    }
}

  lazy数组是用来提高代码的效率,即当搜到应该修改的区间时(假设这个节点的序号为now),暂时不修改其sum[](记录值的数组),而是lazy[now]+=d(注意是+=不是=,我错了好几次),并且不再修改在now下方的节点。那么对于now上方的所有点,就都要加上我要修改的数的总数(即R-L)和我要增加的数的乘积(应该很好理解)。

  如果修改的区间在两个分支,那么我们分开继续搜就可以了,搜到了就操作lazy并且返回即可。

  注意:

    1.此时的R,L为将要修改的区间的两个端点,所以sum[]+=(R-L)*d。

    2.找到对应区间后,并没有修改sum[]值,所以以后再次碰到lazy的时候要修改其sum[],并且lazy下传(后话一会儿再说)。

    3.对于区间的平分问题:是要将整个区间平分,所以mid=(l[now]+r[now])/2,而不是R和L(对于每一个mid的求值,操作的都应是l[now]和r[now])。

  最后是求和:(加油马上就完事了~>\<)

ll query(int L,int R,int now)
{
    sum[now]+=(long long)(r[now]-l[now]+1)*lazy[now];
    lazy[now*2]+=lazy[now];
    lazy[now*2+1]+=lazy[now];
    lazy[now]=0;
    if(l[now]==L&&r[now]==R)
    {
        return sum[now];
    }
    int mid=(l[now]+r[now])/2;
    if(R<=mid)
        return query(L,R,now*2);
    else if(mid<L)
        return query(L,R,now*2+1);
    else
        return query(L,mid,now*2)+query(mid+1,R,now*2+1);
}

  对于每一个搜到的点,都要加上它的lazy(反正没有lazy就是+0,没影响的),并且注意(敲黑板了):所有有lazy数组的点,它下面的点或多或少都会有本该操作却没有操作的点,它们本该加上的值就被记录在了现在搜到的这个点里,所以我们要将lazy数组下传(并且清零!)。

  注意:

    1.于lazy的下传:因为lazy存在说明这个点的整个区间都需要修改,所以为sum[]+=lazy[now]*(r[now]-l[now]+1),而不是R和L,它们只是我们要求和的点罢了,碰到它们,直接返回sum[]就可以。

    2.碰到一个sum[],即使有lazy也要(更要)修改其sum,因为之前的修改函数,对于正好的区间,是只记录了lazy而没修改sum。

  好啦好啦,该说的都说了,手也累了,希望能帮到大家!

  最后还是完整代码:

#include<cstdio>
using namespace std;
#define maxn 100005
#define ll long long
int l[4*maxn],r[4*maxn];
ll lazy[8*maxn],sum[4*maxn];
void build(int L,int R,int now)
{
    l[now]=L;
    r[now]=R;
    //lazy[now]=0;
    if(L==R)
    {
        scanf("%lld",&sum[now]);
        return ;
    }
    int mid=(L+R)/2;
    build(L,mid,now*2);
    build(mid+1,R,now*2+1);
    sum[now]=sum[now*2]+sum[now*2+1];
}
void update(int L,int R,ll d,int now)
{
    if(l[now]==L&&r[now]==R)
    {
        lazy[now]+=d;
        return ;
    }
    sum[now]+=(long long)d*(R-L+1);
    int mid=(l[now]+r[now])/2;
    if(R<=mid)
        update(L,R,d,now*2);
    else if(mid<L)
        update(L,R,d,now*2+1);
    else
    {
        update(L,mid,d,now*2);
        update(mid+1,R,d,now*2+1);
    }
}
ll query(int L,int R,int now)
{
    sum[now]+=(long long)(r[now]-l[now]+1)*lazy[now];
    lazy[now*2]+=lazy[now];
    lazy[now*2+1]+=lazy[now];
    lazy[now]=0;
    if(l[now]==L&&r[now]==R)
    {
        return sum[now];
    }
    int mid=(l[now]+r[now])/2;
    if(R<=mid)
        return query(L,R,now*2);
    else if(mid<L)
        return query(L,R,now*2+1);
    else
        return query(L,mid,now*2)+query(mid+1,R,now*2+1);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--)
    {
        int op,a,b;
        scanf("%d%d%d",&op,&a,&b);
        if(op==1)
        {
            ll x;
            scanf("%lld",&x);
            update(a,b,x,1);
        }
        else
            printf("%lld
",query(a,b,1));
    }
    return 0;
}
原文地址:https://www.cnblogs.com/popo-black-cat/p/10022238.html