线段树2:区间乘法实现

之前的线段树1相信大家都做过了。线段树1要求能够实现区间加法和区间查询,那如果再添加一项区间乘呢?


显然,之前我们引入的懒标记是为了更快速的实现区间修改。
但这也仅仅是对于同等级的运算
因为不同运算等级的运算是无法累积同一懒标记的。
应该很好理解。就好比你要把你的孩子结点进行区间乘操作,但是你只是把你存储的懒标记乘了一下,是无法达到效果的。


考虑引入两个懒标记。
一个负责存储区间加的懒标记,另外一个负责存储区间乘的。
这样down操作就会变得更加繁琐,因为需要维护的东西多了一个~~ 这谁撑得住 ~~
说说具体实现吧。
题目由三种操作。
区间查询,加,乘。
我们自己需要添加一个down,懒标记下传。


首先建树。~~ 我在这个坑里卡了整整1day ~~
先循环输入n次,用a数组临时存储下来。
然后build(建树)

void build(long long k,long long l,long long r)//建树 
{
    tree[k].l=l,tree[k].r=r;
    tree[k].add_=0,tree[k].mul_=1;
    if(l==r)
        tree[k].w=a[l];
    else
    {
        int m=(l+r)/2;
        build(k*2,l,m);
        build(k*2+1,m+1,r);
        tree[k].w=tree[k*2].w+tree[k*2+1].w;
    }
    tree[k].w%=p;
    return;
}

建树和1差不多,不过因为这次是用a数组临时存储的,所以就把scanf改成了赋值而已。另外多了个取模的操作。一样是二分,从根节点开始,往自己的孩子递归,直到叶子结点,然后给叶子结点赋值之后,返回更新当前结点的值。

接下来就是如何同时实现区间乘和区间加了。
虽然有取模操作,但是加法和乘法对于取模这件事是自由的。
但是两种不同运算等级的在线修改怎么实现呢?
引入上面说的两个懒标记。
然后考虑计算的优先级。
因为就只有乘法和加法,分开讨论就行了。
1.加法优先。
会导致一些应该乘的部分没有完成,以及各种奇奇怪怪的问题。
2.乘法优先。
这样的话,修改加法懒标记时就修改,乘法的时候就顺带修改一下加法的就可以了。
似乎2看起来明显比1要好。
那就选择乘法优先!
我和之前一样,结构体里存储了基本的:左端点,右端点,区间值,还加上了两个懒标记。

struct node
{
    long long l,r,w;
    long long add_,mul_;//懒标记 
}tree[4*100000+10];

接下来讲下传操作。
每次需要更新的:
两个儿子结点的区间值、加法懒标记和乘法懒标记,以及清空父节点的懒标记。
因为是乘法优先,那么顺序是:区间值、乘法、加法、清空。
看起来很繁琐实际上只要理解了顺序就行。

void down(long long k)//双懒标记下传 
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    tree[k*2].w=(tree[k*2].w*tree[k].mul_+(m-l+1)*tree[k].add_)%p;
    tree[k*2+1].w=(tree[k*2+1].w*tree[k].mul_+(r-m)*tree[k].add_)%p;
    tree[k*2].mul_=(tree[k*2].mul_*tree[k].mul_)%p;
    tree[k*2+1].mul_=(tree[k*2+1].mul_*tree[k].mul_)%p;
    tree[k*2].add_=(tree[k*2].add_*tree[k].mul_+tree[k].add_)%p;
    tree[k*2+1].add_=(tree[k*2+1].add_*tree[k].mul_+tree[k].add_)%p;
    tree[k].mul_=1;
    tree[k].add_=0;
    return;
}

两个在线修改操作也和之前一样就行了。依然是二分
cppvoid mu(long long k) { long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2; if(y=r) { tree[k].w=(tree[k].w*mul)%p; tree[k].mul_=(tree[k].mul_*mul)%p; tree[k].add_=(tree[k].add_*mul)%p; return; } down(k); mu(k*2); mu(k*2+1); tree[k].w=(tree[k*2].w+tree[k*2+1].w)%p; return; } void ad(long long k) { long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2; if(y=r) { tree[k].w=(tree[k].w+add*(r-l+1))%p; tree[k].add_=(tree[k].add_+add)%p; return; } down(k); ad(k*2); ad(k*2+1); tree[k].w=(tree[k*2].w+tree[k*2+1].w)%p; return; }
最后是区间查询,还是二分,查到就加上返回。

long long ask(long long k)
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    if(y=r)return tree[k].w;
    down(k);
    return (ask(k*2)+ask(k*2+1))%p;
}

题目门
完整代码贴下面:

using namespace std;
long long n,m,x,y,mul,add;
struct node
{
    long long l,r,w;
    long long add_,mul_;//懒标记 
}tree[4*100000+10];
long long p,a[100010];
void build(long long k,long long l,long long r)//建树 
{
    tree[k].l=l,tree[k].r=r;
    tree[k].add_=0,tree[k].mul_=1;
    if(l==r)
        tree[k].w=a[l];
    else
    {
        int m=(l+r)/2;
        build(k*2,l,m);
        build(k*2+1,m+1,r);
        tree[k].w=tree[k*2].w+tree[k*2+1].w;
    }
    tree[k].w%=p;
    return;
}
void down(long long k)//双懒标记下传 
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    tree[k*2].w=(tree[k*2].w*tree[k].mul_+(m-l+1)*tree[k].add_)%p;
    tree[k*2+1].w=(tree[k*2+1].w*tree[k].mul_+(r-m)*tree[k].add_)%p;
    tree[k*2].mul_=(tree[k*2].mul_*tree[k].mul_)%p;
    tree[k*2+1].mul_=(tree[k*2+1].mul_*tree[k].mul_)%p;
    tree[k*2].add_=(tree[k*2].add_*tree[k].mul_+tree[k].add_)%p;
    tree[k*2+1].add_=(tree[k*2+1].add_*tree[k].mul_+tree[k].add_)%p;
    tree[k].mul_=1;
    tree[k].add_=0;
    return;
}
void mu(long long k)
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    if(y=r)
    {
        tree[k].w=(tree[k].w*mul)%p;
        tree[k].mul_=(tree[k].mul_*mul)%p;
        tree[k].add_=(tree[k].add_*mul)%p;
        return;
    }
    down(k);
    mu(k*2);
    mu(k*2+1);
    tree[k].w=(tree[k*2].w+tree[k*2+1].w)%p;
    return;
}
void ad(long long k)
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    if(y=r)
    {
        tree[k].w=(tree[k].w+add*(r-l+1))%p;
        tree[k].add_=(tree[k].add_+add)%p;
        return;
    }
    down(k);
    ad(k*2);
    ad(k*2+1);
    tree[k].w=(tree[k*2].w+tree[k*2+1].w)%p;
    return;
}
long long ask(long long k)
{
    long long l=tree[k].l,r=tree[k].r,m=l+(r-l)/2;
    if(y=r)return tree[k].w;
    down(k);
    return (ask(k*2)+ask(k*2+1))%p;
}
int main()
{
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    build(1,1,n);
    while(m--)
    {
        int ord;
        cin>>ord;
        if(ord==1)
        {
            cin>>x>>y>>mul;
            mu(1);
        }
        else if(ord==2)
        {
            cin>>x>>y>>add;
            ad(1);
        }
        else if(ord==3)
        {
            cin>>x>>y;
            cout<

ov.

你可能感兴趣的:(线段树2:区间乘法实现)