线段树2对于Pushdown的理解

关于“线段树2”~3373

最近才把这玩意儿搞出来,代码和解说如下:

    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=100005;
    struct sd{
        int son[2],l,r;
        long long sum,add,pass;
        sd()
        {
            memset(this,0,sizeof(this));
            pass=1;
        }
    }node[N*2];
    int ini[N],root,cnt=0,q;
    void Buildtree(int &k,int l,int r)
    {
        k=++cnt;
        node[k].l=l;
        node[k].r=r;
        if(l==r)
        {
            node[k].sum=ini[l];
        }
        else
        {
            int mid=(l+r)/2;
            Buildtree(node[k].son[0],l,mid);
            Buildtree(node[k].son[1],mid+1,r);
            node[k].sum=node[node[k].son[0]].sum+node[node[k].son[1]].sum;
        }
    }
    void pushdown(int k)
    {
        sd L,R,Main;
        L=node[node[k].son[0]];
        R=node[node[k].son[1]];
        Main=node[k];
        L.sum=(L.sum*Main.pass+Main.add*(L.r-L.l+1))%q;
        R.sum=(R.sum*Main.pass+Main.add*(R.r-R.l+1))%q;
        L.pass=(L.pass*Main.pass)%q;
        R.pass=(R.pass*Main.pass)%q;
        L.add=(L.add*Main.pass+Main.add)%q;
        R.add=(R.add*Main.pass+Main.add)%q;
        Main.add=0; Main.pass=1;
        node[k]=Main;//☣
        node[node[k].son[0]]=L;
        node[node[k].son[1]]=R;
        return;
    }
    void modify1(int k,int ql,int qr,int val)
    {
        if(node[k].l==ql&&node[k].r==qr)
        {
            node[k].add=(node[k].add+val)%q;
            node[k].sum=(node[k].sum+val*(node[k].r-node[k].l+1))%q;
        }
        else
        {
            pushdown(k);
            int mid=(node[k].l+node[k].r)/2;
            if(ql>mid) modify1(node[k].son[1],ql,qr,val);
            else if(qr<=mid) modify1(node[k].son[0],ql,qr,val);
            else 
            {
                modify1(node[k].son[0],ql,mid,val);
                modify1(node[k].son[1],mid+1,qr,val);
            }
            node[k].sum=node[node[k].son[0]].sum+node[node[k].son[1]].sum;
        }
    }
    void modify2(int k,int ql,int qr,int val)
    {
        if(node[k].l==ql&&node[k].r==qr)
        {
            //node[k].add=(node[k].add*val)%q;//一个存疑点
            node[k].pass=(node[k].pass*val)%q;
            node[k].sum=(node[k].sum*val)%q;
        }
        else
        {
            pushdown(k);
            int mid=(node[k].l+node[k].r)/2;
            if(ql>mid) modify2(node[k].son[1],ql,qr,val);
            else if(qr<=mid) modify2(node[k].son[0],ql,qr,val);
            else 
            {
                modify2(node[k].son[0],ql,mid,val);
                modify2(node[k].son[1],mid+1,qr,val);
            }
            node[k].sum=node[node[k].son[0]].sum+node[node[k].son[1]].sum;
        }
    }
    long long query(int k,int l,int r,int n)
    {
        if(node[k].l==l&&node[k].r==r)
        {
            pushdown(k);
            return node[k].sum;
        }
        else
        {
            pushdown(k);
            int mid=(node[k].l+node[k].r)/2;
            if(mid>=r) return query(node[k].son[0],l,r,n);
            else if(mid<l) return query(node[k].son[1],l,r,n);
            else 
            {
                return query(node[k].son[0],l,mid,n)+query(node[k].son[1],mid+1,r,n);
            }
        }
    }
    int main()
    {
        int m,n;
        scanf("%d%d%d",&m,&n,&q);
        int a,b,c,d;
        for(int i=1;i<=m;++i)
        {
            scanf("%d",&ini[i]);
        }
        Buildtree(root,1,m+5);
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&d);
            if(d==1)
            {
                scanf("%d%d%d",&a,&b,&c);
                modify2(root,a,b,c);
            }
            else if(d==2)
            {
                scanf("%d%d%d",&a,&b,&c);
                modify1(root,a,b,c);
            }
            else
            {
                scanf("%d%d",&a,&b);
                printf("%lld
",query(root,a,b,q)%q);
            }
        }
        return 0;
    }

其中Pushdown是本题的精髓


正常的两种Pushdown写法:

方法一:

    void pushdown(int k)
    {
        sd L,R,Main;
        L=node[node[k].son[0]];
        R=node[node[k].son[1]];
        Main=node[k];
        L.sum=(L.sum*Main.pass+Main.add*(L.r-L.l+1))%q;
        R.sum=(R.sum*Main.pass+Main.add*(R.r-R.l+1))%q;
        L.pass=(L.pass*Main.pass)%q;
        R.pass=(R.pass*Main.pass)%q;
        L.add=(L.add*Main.pass+Main.add)%q;
        R.add=(R.add*Main.pass+Main.add)%q;
        Main.add=0; Main.pass=1;
        node[k]=Main;
        node[node[k].son[0]]=L;
        node[node[k].son[1]]=R;
        return;
    }

方法二:

    void Pushdown(int k)
    {
        node[node[k].son[0]].sum=(node[node[k].son[0]].sum*node[k].pass + node[k].add* ( node[node[k].son[0]].r - node[node[k].son[0]].l+1) ) %q;
           node[node[k].son[1]].sum=(node[node[k].son[1]].sum*node[k].pass + node[k].add*( node[node[k].son[1]].r - node[node[k].son[1]].l+1) ) %q;
           node[node[k].son[0]].pass=(node[node[k].son[0]].pass*node[k].pass)%q;
           node[node[k].son[1]].pass=(node[node[k].son[1]].pass*node[k].pass)%q;
        node[node[k].son[0]].add=(node[node[k].son[0]].add*node[k].pass+node[k].add)%q;
           node[node[k].son[1]].add=(node[node[k].son[1]].add*node[k].pass+node[k].add)%q;
           node[k].add=0; node[k].pass=1;
           return 0;
    }

WA掉的方法:

    void Pushdown(int k)
    {
        node[node[k].son[0]].add+=node[k].add;//写错了
        node[node[k].son[1]].add+=node[k].add;//写错了
        node[node[k].son[0]].pass*=node[k].pass;
        node[node[k].son[1]].pass*=node[k].pass;
        node[node[k].son[0]].sum=node[node[k].son[0]].sum*node[node[k].son[0]].pass+(node[node[k].son[0]].r-node[node[k].son[0]].l+1);
        node[node[k].son[1]].sum=node[node[k].son[1]].sum*node[node[k].son[1]].pass+(node[node[k].son[1]].r-node[node[k].son[1]].l+1);
        node[k].add=0;  node[k].pass=1;
        return 0;
    }

如何理解此Pushdown呢(大家可以对照那份WA掉的看)?


首先前两行是来更新父节点下左右儿子的和一定要遵循*先乘后加*的原则,并且在加的时候,一定不忘乘上区间的长度这样更新才不会错!!后面四步是基于要“薪火相传”的原则,以后Pushdown的时候可以Pushdown给他们自己的儿子。

所以中间两步是先来传乘法(pass)直接用儿子的乘(L&R.pass)乘上父亲转下来的乘(Main.pass)。

后面两步则是传下父亲的加,但是要注意,传加的时候应该先用儿子原有的加乘上父亲的乘(因为我们遵循“*先乘后加*”的原则,在第一阶段,原有的加应该会被父亲的乘成倍扩大(和区间和(sum)一起)!!!)

关于modify1,modify2中的改变

本人写程序的时候,可能脑袋有点抽,modify1和操作1不是对应的,modify1对应操作2~~~~~~

先来看一下和乘有关的modify
    void modify2(int k,int ql,int qr,int val)
    {
        if(node[k].l==ql&&node[k].r==qr)
        {
            node[k].add=(node[k].add*val)%q;
            node[k].pass=(node[k].pass*val)%q;
            node[k].sum=(node[k].sum*val)%q;
        }
        //后面还有省略,这里只是相对于线段树1有稍微改变的地方

乘法modify主要要注意的问题,还是要注意要更新加(add)中的东西(我想了一下,介于有Pushdown我也觉得没有那个必要)然后就是正常更新儿子中乘(pass)和总和(sum)。

再来看一下和加有关的modify
    }    void modify1(int k,int ql,int qr,int val)
    {
        if(node[k].l==ql&&node[k].r==qr)
        {
            node[k].add=(node[k].add+val)%q;
            node[k].sum=(node[k].sum+val*(node[k].r-node[k].l+1))%q;
        }
        //后面还有省略,这里只是相对于线段树1有稍微改变的地方

正常修改儿子中的加(add),但是最后一定不要忘记修改儿子总和时(sum)(因为有Pushdown,所以我们不用考虑“先乘后加”原则),只要记住乘上区间长度即可。

最后(几点提醒)

1、Pushdown中一定不要忘了把建立的临时变量里的东西传回node中!!!血的教训啊!耗了我一个多小时!!!*

2、modify中Pushdown已经帮你做了很多事情了,所以不用think to much!

3、更多警告可以看一下我的线段树Coding Caution

P.S:有人可能会问我,为什么在Pushdown中更新下面的总值的时候为什么不把要更新的点的Pass也乘进去,是因为在modify的时候我已经用他原来的Pass值更新过一遍了,当然不需要乘进去,那又会有人问,为什么要把pass的值和add的值又更新到要更新的点的Pass和add上呢?这个则是因为后面又要Pushdown的时候,一定要连着这个一起Pushdown下去。

2018.10.16

下面放一个写的比较清晰的线段树2:

AC code:

#include<bits/stdc++.h>
#define N 100004
#define LL long long
using namespace std;
struct sd{
    LL son[2],l,r,pass,add,sum;
    sd(){pass=1;}
}node[N*10];
LL p,ini[N],root,cnt,n,m;
void update(LL k){node[k].sum=node[node[k].son[0]].sum+node[node[k].son[1]].sum;}
void add_pass(LL k,LL val)
{
    node[k].pass=(node[k].pass*val)%p;node[k].sum=(node[k].sum*val)%p;
    node[k].add=(node[k].add*val)%p;
}
void add_add(LL k,LL val)
{
    node[k].sum=(val*(node[k].r-node[k].l+1)+node[k].sum)%p;
    node[k].add=(node[k].add+val)%p;
}
void pushdown(LL k)
{
    add_pass(node[k].son[0],node[k].pass); add_pass(node[k].son[1],node[k].pass);node[k].pass=1;
    add_add(node[k].son[0],node[k].add); add_add(node[k].son[1],node[k].add);node[k].add=0;
}
void Buildtree(LL &k,LL l,LL r)
{
    k=++cnt;node[k].l=l;node[k].r=r;
    if(l==r) node[k].sum=ini[l];
    else {LL mid=(l+r)/2;Buildtree(node[k].son[0],l,mid);Buildtree(node[k].son[1],mid+1,r);update(k);}
}
void modify_pass(LL k,LL l,LL r,LL val)
{
    if(node[k].l==l&&node[k].r==r) add_pass(k,val);
    else
    {
        pushdown(k); LL mid=(node[k].l+node[k].r)/2;
        if(r<=mid) modify_pass(node[k].son[0],l,r,val);
        else if(l>mid) modify_pass(node[k].son[1],l,r,val);
        else modify_pass(node[k].son[0],l,mid,val),modify_pass(node[k].son[1],mid+1,r,val);
        update(k);
    }
}
void modify_add(LL k,LL l,LL r,LL val)
{
    if(node[k].l==l&&node[k].r==r) add_add(k,val);
    else
    {
        pushdown(k); LL mid=(node[k].l+node[k].r)/2;
        if(r<=mid) modify_add(node[k].son[0],l,r,val);
        else if(l>mid) modify_add(node[k].son[1],l,r,val);
        else modify_add(node[k].son[0],l,mid,val),modify_add(node[k].son[1],mid+1,r,val);
        update(k);
    }
}
LL query(LL k,LL l,LL r)
{
    if(node[k].l==l&&node[k].r==r) return node[k].sum%p;
    else
    {
        pushdown(k); LL mid=(node[k].l+node[k].r)/2;
        if(r<=mid) return query(node[k].son[0],l,r)%p;
        else if(l>mid) return query(node[k].son[1],l,r)%p;
        else return (query(node[k].son[0],l,mid)+query(node[k].son[1],mid+1,r))%p;
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=n;++i) scanf("%d",&ini[i]);
    Buildtree(root,1,n);int a,b,c,d;
    for(int i=1;i<=m;++i)
    {
        scanf("%d",&a);
        if(a==1) scanf("%d%d%d",&b,&c,&d),modify_pass(root,b,c,d);
        if(a==2) scanf("%d%d%d",&b,&c,&d),modify_add(root,b,c,d);
        if(a==3) scanf("%d%d",&b,&c),printf("%d
",query(root,b,c));
    }
    return 0;
}

指针版的线段树戳这里☞指针版的线段树

谢谢采纳!
原文地址:https://www.cnblogs.com/mudrobot/p/13330784.html