[AHOI2009]维护序列 题解

[AHOI2009]维护序列

【模板】线段树 2

这两题基本相同,所以就放在一起讲

题目

维护一个序列,要求支持一下三种操作

  1. 区间加一个数
  2. 区间乘一个数
  3. 区间求和

solution

首先这是线段数,不要问为什么。 因为很显然

我们都知道线段树的一个基操,就是懒惰标记

这道题我们需要维护两个懒惰标记\(add\)\(mul\)

对于一个区间的值我们用\(value\times mul + (r-l+1)\times add\)来表示

区间加\(x\)就可以表示为\(value \times mul + (r-l+1)\times (add + x)\)

也即把\(add\)标记变为\(add+x\)

区间乘\(x\)就可以表示为\(value \times mul \times x + (r-l+1)\times add \times x\)

也即把\(add\)标记变为\(add\times x\),把\(mul\)标记变为\(mul\times x\)

这里要注意一点因为运算的优先级所以要先维护mul再维护add

coding

#include 
#define LL long long
using namespace std;


const int N = 100005;
LL n,m,p,a[N];

struct Node
{
    LL l,r,value,mul,add;
    Node * left , * right;
    Node(LL s , LL t , LL v , Node * a , Node * b)
    {
        l = s , r = t , value = v , mul = 1 , add = 0;
        left = a , right = b;
    }
} * root;


inline LL read()
{
    register LL x = 0;
    register char ch = getchar();
    while(ch < '0' || ch > '9') ch = getchar();
    while(ch >= '0' && ch <= '9')
    {
        x = (x<<3)+(x<<1) + ch-'0';
        ch = getchar();
    }
    return x;
}



inline Node * build( LL l , LL r)
{
    if(l == r) return new Node( l , r , a[l] % p,0 , 0 );
    register LL mid = ( l + r) >> 1;
    Node * left = build( l , mid ) , * right = build( mid + 1 , r);
    return new Node(l , r , (left -> value + right -> value) % p , left , right);
}

inline LL get_value(Node * cur)
{
    return ( cur -> value * cur -> mul + cur -> add * (cur -> r - cur -> l + 1) ) % p;
}

inline void mark(LL add , LL mul , Node * cur)
{   
    
    cur -> add = ( cur -> add * mul + add ) % p;
    cur -> mul = ( cur -> mul * mul) % p;
    cur -> value = (cur -> value * mul + ( cur -> r - cur -> l + 1) * add ) % p;
    //这里要注意修改去区间的值不能用标记的值,要用增加的值 
    return ;
}



inline void pushdown( Node * cur)
{
    if(cur -> mul == 1 && cur -> add == 0) return ;
    if(cur -> left)//如果有儿子节点,不是叶子节点 
    {
        mark( cur -> add , cur -> mul , cur -> left);
        mark( cur -> add , cur -> mul , cur -> right);
    }
    else cur -> value = cur -> value * cur -> mul + cur -> add;
    //如果是叶子节点这不用标记下传,直接修改 
    cur -> mul = 1; cur -> add = 0;
    //下传后情况标记 
    return ;
}

inline LL query( LL l , LL r , Node * cur)
{
    if( l <= cur -> l && cur -> r <= r) return cur -> value % p;
    register LL mid = (cur -> l + cur -> r) >> 1, res = 0;
    pushdown( cur );
    //因为要访问子节点所以先把标记下传 
    if( l <= mid ) res += query( l , r , cur -> left) % p;
    if( mid + 1 <= r ) res += query( l , r , cur -> right) % p;
    return res % p;
}

inline void modify( LL l , LL r , LL add , LL mul , Node * cur)
{
    if(cur -> l > r || cur -> r < l) return ;
    if(l <= cur -> l && cur -> r <= r)
    {
        mark( add , mul , cur);
        return ;
    }
    if( cur -> add != 0 || cur -> mul != 1 ) pushdown( cur );
    //因为要访问子节点所以先把标记下传 
    register LL mid = (cur -> l + cur -> r) >> 1;
    if( l <= mid ) modify( l , r , add , mul , cur -> left);
    if( mid + 1 <= r ) modify( l , r , add ,mul , cur -> right);
    cur -> value = ( cur -> left -> value + cur -> right -> value) % p;
    //用子节点的值更新当前区间的值 
}





int main()
{
    n = read();m = read(); p = read();
    for(register int i = 1 ; i <= n ; i ++) a[i] = read();
    root = build( 1 , n );
    
    while( m -- )
    {
        register LL opt = read();
        if( opt == 1 )
        {
            register LL l = read() , r = read() , v = read();
            modify( l , r , 0 , v , root );
        }
        else if( opt == 2)
        {
            register LL l = read() , r = read() , v = read();
            modify( l , r , v , 1 , root );
        }
        else 
        {
            register LL l = read() , r = read();
            printf("%lld\n", query( l , r , root ));
        }
    }
    return 0;
}

你可能感兴趣的:([AHOI2009]维护序列 题解)