洛谷P2023 (线段树)题解

题目链接:https://www.luogu.org/problem/P2023

题意

对一组数列进行三种操作

  1. 对某一段区间的每一个数,扩大某一个倍数
  2. 对某一段区间的每一个数,增加某一个增量
  3. 询问某一段区间的和

题解:线段树

  令 add 和 mul 表示区间每个数需要加上的数和乘上的数,目的是对自区间进行加法或者乘的操作,分配之后会归位为 0 和 1,表示已经分配完毕。
  如果只有加法一种操作,很明显用一个lazy标记就可以得到答案,sum+=add*len,插入或查找的时候push一下,将add分配下去。
  如果增加乘法操作,则:

sum=(sum+add*len)*mul
sum=sum*mul+add*len*mul
(len为区间长度,len=right-left+1)

所以将sum看成两部分,一种是加,一种是乘,而且每次只会进行其中一种操作。

  1. 如果是加操作,直接对结点的add值操作即可。
  2. 如果是乘操作,则需要如上变形。首先对 add 进行计算,更新当前 sum ,更新 mul ,需要注意 add和mul是对下一个子区间的一种操作,设子区间的当前和为sum0,则
    sum0=mul*sum0+add*len

    这个计算在push中体现,每一个push是对下一个子区间的操作,所以每次计算 add 的时候不能仅仅是node[num*2].add+=node[num].add ,但很明显每次乘积之后,如果子区间的add不为0,那么分配的add=add*mul,也就是多次进行加操作之后再乘,每次的加操作也就翻了一番。
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define ll long long
#define inf 1000000009000000000
#define IOS ios::sync_with_stdio(false)

ll mod;
struct e
{
    ll left, right; //该节点的区间范围
    ll add;  //区间增量
    ll mul;  //区间乘量
    ll sum;  //区间和,包括add在内
};

e tree[4*maxn];
ll a[maxn];//记录原始值

//建树,从上往下建
ll build(ll s, ll t, ll num)
{
    ll i, j;
    tree[num].left = s;
    tree[num].right = t;
    tree[num].add = 0;
    tree[num].mul=1;
    if (s != t)
    {
        i = build(s, (s + t) / 2, num * 2);
        j = build((s + t) / 2 + 1, t, num * 2 + 1);
    }
    else//s等于t说明是最底层
    {
        tree[num].sum = a[s];
        return(a[s]);
    }
    tree[num].sum = i + j;//求和
    tree[num].sum%=mod;
    return(tree[num].sum);
}

void insert(ll s, ll t, ll add,ll mul,ll num)
{
    //如果发现是s,t的子集,标记并修改后返回
    if (s <= tree[num].left&&tree[num].right <= t)
    {
        tree[num].add*=mul;
        tree[num].add += add;
        tree[num].sum*=mul;
        tree[num].sum += add * (tree[num].right - tree[num].left + 1);
        tree[num].sum%=mod;
        tree[num].mul*=mul;
        tree[num].add%=mod;
        tree[num].mul%=mod;
        //cout<<"add="<<tree[num].add<<endl;
        return;
    }

    //发现不是s,t的子集,检查是否被标记过,分散到左右子节点中,重置add
    //为什么要执行这个操作,因为后面需要标记它后面的某个子集,而后面的子集原来都是没标记的,所以必须提前分配
    if (tree[num].add||tree[num].mul!=1)
    {
        tree[num*2].sum*=tree[num].mul;
        tree[num * 2].sum += tree[num].add*(tree[num * 2].right - tree[num * 2].left + 1);
        tree[num*2].sum%=mod;

        tree[num*2+1].sum*=tree[num].mul;
        tree[num * 2 + 1].sum += tree[num].add*(tree[num * 2 + 1].right - tree[num * 2 + 1].left + 1);
        tree[num*2+1].sum%=mod;

        tree[num*2].mul*=tree[num].mul;
        tree[num*2+1].mul*=tree[num].mul;

        tree[num*2].add*=tree[num].mul;
        tree[num * 2].add += tree[num].add;
        
        tree[num*2+1].add*=tree[num].mul;
        tree[num * 2 + 1].add += tree[num].add;

        tree[num*2].mul%=mod;
        tree[num*2+1].mul%=mod;
        tree[num * 2].add %=mod;
        tree[num * 2 + 1].add %=mod;

        tree[num].add = 0;
        tree[num].mul=1;
    }

    if (s <= (tree[num].left + tree[num].right) / 2)
        insert(s, t, add, mul,num * 2);
    if (t>(tree[num].left + tree[num].right) / 2)
        insert(s, t, add, mul,num * 2 + 1);
    tree[num].sum = tree[num * 2].sum + tree[num * 2 + 1].sum;
    tree[num].sum%=mod;
}

ll search(ll s, ll t, ll num)
{
    ll i = 0, j = 0;
    //搜索到是其子集,返回sum
    if (s <= tree[num].left&&tree[num].right <= t)
    {
        //cout<<tree[num].left<<" "<<tree[num].right<<" "<<tree[num].sum<<endl;
        return(tree[num].sum);
    }
    //与修改一样,必须分配下去
    if (tree[num].add||tree[num].mul!=1)
    {
        tree[num*2].sum*=tree[num].mul;
        tree[num * 2].sum += tree[num].add*(tree[num * 2].right - tree[num * 2].left + 1);
        tree[num*2].sum%=mod;

        tree[num*2+1].sum*=tree[num].mul;
        tree[num * 2 + 1].sum += tree[num].add*(tree[num * 2 + 1].right - tree[num * 2 + 1].left + 1);
        tree[num*2+1].sum%=mod;

        tree[num*2].mul*=tree[num].mul;
        tree[num*2+1].mul*=tree[num].mul;
        tree[num*2].add*=tree[num].mul;
        tree[num * 2].add += tree[num].add;
        tree[num*2+1].add*=tree[num].mul;
        tree[num * 2 + 1].add += tree[num].add;

        tree[num*2].mul%=mod;
        tree[num*2+1].mul%=mod;
        tree[num * 2].add %=mod;
        tree[num * 2 + 1].add %=mod;

        tree[num].add = 0;
        tree[num].mul=1;
    }
    if (s <= (tree[num].left + tree[num].right) / 2)
    {
        i = search(s, t, num * 2);
        i%=mod;
    }
    if (t>(tree[num].left + tree[num].right) / 2)
    {
        j = search(s, t, num * 2 + 1);
        j%=mod;
    }

    return(i + j)%mod;
}

int main()
{
    IOS;
    ll k, n, m, p, t;
    cin>>n>>mod;
    for (ll i = 1; i <= n; i++)
        cin>>a[i];
    build(1, n, 1);
    cin>>m;
    for (ll i = 1; i <= m; i++)
    {
        ll s,a,b,c;
        cin>>s;
        if(s==1)
        {
            cin>>a>>b>>c;
            c%=mod;
            insert(a,b,0,c,1);
        }
        else if (s == 2)
        {
            cin>>a>>b>>c;
            c%=mod;
            insert(a, b, c, 1, 1);
        }
        else
        {
            cin>>a>>b;
            cout << search(a, b, 1) << endl;
        }
    }

    return(0);
}

发布了41 篇原创文章 · 获赞 2 · 访问量 1246

猜你喜欢

转载自blog.csdn.net/qq_41418281/article/details/100542160
今日推荐