[AHOI2009]维护序列
【模板】线段树 2
这两题基本相同,所以就放在一起讲
题目
维护一个序列,要求支持一下三种操作
- 区间加一个数
- 区间乘一个数
- 区间求和
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;
}