线段树lazy标记:加乘混合

Posted wzx-rs-sthn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树lazy标记:加乘混合相关的知识,希望对你有一定的参考价值。

题目

Description

给定一个正整数序列A,要求支持以下操作 
1):  + a b c    表示在[a,b]上加上一个常数C。 
2):  * a b c    在[a,b]上乘上一个常数K。 
3):  QUERY a b  查询[a,b]的sum。 

Input

第一行两个正整数n、m,n表示序列长度,m表示操作数 
第二行n个正整数,第i表示A[i]的大小 
接下来的m行,每行有且仅有一种操作,具体和题目描述一致? 
n,m<=100000 
其他权值都<=50000 
小心爆int 

Output

对于每个询问操作,输出答案对1000000007取余的结果 

Sample Input

10 10
50 14 20 18 19 11 43 43 26 44
+ 3 6 41
QUERY 1 2
+ 5 5 14
QUERY 1 3
QUERY 4 4
QUERY 2 6
* 3 5 31
* 4 8 20
* 5 8 28
QUERY 6 7

Sample Output

64
125
59
260
53200

思路:

这是一道很明显的线段树修改题;

写这题前你必须知道普通的lazy标记怎么写;

那么这题有什么不一样呢,很显然是要考虑加乘的顺序;

如(a+b)*c ,a为线段树某一区间和,b为这个区间的 加法lazy标记,c为要乘上的数;

那么就要 a*c+b*c ,而不是 a*c+b;

所以我们不仅需要 把a(也就是某一区间和)*c 还要把b(这个区间的加法lazy标记)*c;

当然除了下传加法标记,也要下传乘法标记;

所以这一题不一样的是只要在下传乘法标记的时候,把加法标记也乘个c就ok了


代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<0||c>9) {if (c==-) f=-1; c=getchar();}
    while (c>=0&&c<=9) {a=a*10+c-0; c=getchar();}
    return a*f;
}
const ll mod=1000000007;
ll n,m,aa[1000006];
struct sbbb
{
    ll l,r,v,f,ff=1;
}a[5000001];
inline ll R(ll x)//计算x的右节点的编号
{
    return x*2+1;
}
inline ll L(ll x)//计算x的左节点的编号
{
    return x*2;
}
inline void doit(ll p)//区间维护
{
    a[p].v=(a[L(p)].v+a[R(p)].v)%mod;
}
inline void build(ll p,ll l,ll r)//建树
{
    a[p].l=l;a[p].r=r;
    if(l==r)
    {
        a[p].v=aa[l];
        return;
    }
    ll mid=(l+r)>>1;
    build(L(p),l,mid);
    build(R(p),mid+1,r);
    doit(p);
}
inline void push_down(ll p)//标记下传
{
    ll d=a[p].ff;
    a[L(p)].v=(a[L(p)].v*d)%mod;
    a[R(p)].v=(a[R(p)].v*d)%mod;//每个区间和乘上d
    a[L(p)].ff=(a[L(p)].ff*d)%mod;//标记下传
    a[R(p)].ff=(a[R(p)].ff*d)%mod;//标记下传
    a[L(p)].f=(a[L(p)].f*d)%mod;//综“思路”所述,加法标记也要乘上
    a[R(p)].f=(a[R(p)].f*d)%mod;//综“思路”所述,加法标记也要乘上
    a[p].ff=1;
    if(a[p].f)//加法标记的下传
    {
        ll x=a[p].f;
        a[L(p)].v=((a[L(p)].r-a[L(p)].l+1)*x+a[L(p)].v)%mod;
        a[R(p)].v=((a[R(p)].r-a[R(p)].l+1)*x+a[R(p)].v)%mod;
        a[L(p)].f=(a[L(p)].f+x)%mod;
        a[R(p)].f=(a[R(p)].f+x)%mod;
        a[p].f=0;
    }
}
inline void change(ll p,ll l,ll r,ll x)//区间加
{
    if(l<=a[p].l&&r>=a[p].r)
    {
        a[p].v=((a[p].r-a[p].l+1)*x+a[p].v)%mod;
        a[p].f=(a[p].f+x)%mod;
        return;
    }
    push_down(p);//标记下传
    ll mid=(a[p].l+a[p].r)>>1;
    if(l<=mid)
        change(L(p),l,r,x);
    if(r>mid)
        change(R(p),l,r,x);
    doit(p);
}
inline void change1(ll p,ll l,ll r,ll x)//区间乘法
{
    if(l<=a[p].l&&r>=a[p].r)
    {
        a[p].v=(a[p].v*x)%mod;
        a[p].ff=(a[p].ff*x)%mod;
        a[p].f=(a[p].f*x)%mod;//综“思路”所述,加法标记也要乘上
        return;
    }
    push_down(p);
    ll mid=(a[p].l+a[p].r)>>1;
    if(l<=mid)
        change1(L(p),l,r,x);
    if(r>mid)
        change1(R(p),l,r,x);
    doit(p);
}
inline ll findout(ll p,ll l,ll r)//区间统计
{
    if(l<=a[p].l&&r>=a[p].r)
        return a[p].v;
    push_down(p);
    ll mid=(a[p].l+a[p].r)>>1;
    ll sum=0;
    if(l<=mid)
        sum=(sum+findout(L(p),l,r))%mod;
    if(r>mid)
        sum=(sum+findout(R(p),l,r))%mod;
    return sum;
}
int main()
{
    n=read();m=read();
    for(ll i=1;i<=n;i++)
        aa[i]=read();
    build(1,1,n);//冬眠假期刚刚建树,我还有点糊涂
    for(ll i=1;i<=m;i++)
    {
        char c[5];
        scanf("%s",c);
        if(c[0]==Q)
        {
            ll x=read(),y=read();
            ll ans=findout(1,x,y);
            printf("%lld
",ans);
        }
        else if(c[0]==+)
        {
            ll x=read(),y=read(),z=read();
            change(1,x,y,z);
        }
        else
        {
            ll x=read(),y=read(),z=read();
            change1(1,x,y,z);
        }
    }
    return 0;//别忘了return 0;
}

 

 

以上是关于线段树lazy标记:加乘混合的主要内容,如果未能解决你的问题,请参考以下文章

HDU1698 just a Hook - 带有lazy标记的线段树

POJ 3237 Tree (树链剖分 路径剖分 线段树的lazy标记)

线段树初步&&lazy标记

算法模板——线段树之Lazy标记

带有lazy标记的线段树

HDU 1698 Just a Hook 线段树区间更新