[Luogu P2023][JZOJ1663][AHOI2009]维护序列 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/er111er/article/details/79049366

[Luogu P2023][JZOJ1663][AHOI2009]维护序列 题解


题目分析

看题面可以知道题目的要求:
对于一个长度为N的序列a支持以下操作
1.令所有满足l<=i<=r的ai全部变为ai*c。
2.令所有满足l<=i<=r的ai全部变为ai+c
3.求所有满足l<=i<=r的ai的和。
显然这是对一个区间做加法和乘法的操作,可以使用线段树完成。
联想只有区间加法的过程,对于线段树上的一个节点,我们设sum表示该区间的和,inc表示该区间每个数要加上的数,那么该节点所表示的区间和为sum + inc * (r - l + 1)
属于 x+b 这种形式, (rl+1) 可以看作常数。
但是现在区间不仅有加法,还有乘法,因此很容易想到区间和的形式应该为: ax+b
表示现在的区间和是原来的区间和先乘以a再加上b。
在程序中我们把mtp定义为a,sum定义为x,inc定义为b,下传标记时节点的更新就是sum = mtp * sum + inc

区间修改

当我们要修改一个区间时,要保证ax+b的形式,即先乘后加的形式。当将区间乘以一个数k时,原来的区间和为 ax+b ,乘以k得 k(ax+b)=kax+kb ,也就是把节点的inc和mtp都乘上一个k。

区间加一个数更加简单,原来的区间和为 ax+b ,加上一个k为 ax+b+k ,合并b, k得 ax+(b+k) ,也就是把原来的inc加上一个k。

标记下传

我们设要下传标记的节点的inc为 b ,sum为 x ,mtp为 a ,因此这个节点的和为 ax+b ,它的一个儿子的inc为 b ,sum为 y ,mtp为 a ,这个节点的和为 ay+b ,为了保持先乘后加的顺序,先把该节点的和乘以 a

aay+ab
然后加上b得
aay+ab+b
合并一下得
(aa)y+(ab+b)
也就是把这个节点的儿子的mtp乘以这个节点的mtp,然后把这个节点的儿子的inc乘以这个节点的mtp再加上这个节点的inc,更新这个节点,清空这个节点的标记,然后标记就下传完毕了。

代码:

#pragma GCC optimize(2) //O2优化
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define lson rt << 1 //简化书写
#define rson rt << 1 | 1

const int N = 200007; //数据范围有误,100000会RE
typedef long long ll;

int n, q, L, R, op;
ll k, P, a[N];

struct SegmentTree
{
    ll sum[N << 2], mtp[N << 2], inc[N << 2];

    void down(int rt, int l, int r)
    {
        if (mtp[rt] == 1 && inc[rt] == 0) return; //都为初始状态时无需下传
        if (l != r)
            mtp[lson] = mtp[lson] * mtp[rt] % P, //儿子的mtp乘以当前节点mtp
            mtp[rson] = mtp[rson] * mtp[rt] % P, 
            inc[lson] = (inc[lson] * mtp[rt] % P + inc[rt]) % P; //儿子的inc乘以当前节点mtp再加当前节点inc
            inc[rson] = (inc[rson] * mtp[rt] % P + inc[rt]) % P;
        sum[rt] = (sum[rt] * mtp[rt] % P + inc[rt] * (r - l + 1) % P) % P; //更新节点信息
        mtp[rt] = 1, inc[rt] = 0; //清空标记
    }

    void build(int rt, int l, int r) //根据数组构建线段树,当然打insert也是可以的
    {
        mtp[rt] = 1, inc[rt] = 0; //初始化标记
        if (l == r) { sum[rt] = a[l]; return; }
        int mid = l + r >> 1;
        build(lson, l, mid);
        build(rson, mid + 1, r);
        sum[rt] = sum[lson] + sum[rson];
    }

    ll qrysum(int rt, int l, int r)
    {
        down(rt, l, r); //先下传标记
        if (L <= l && r <= R) return sum[rt];
        ll ret = 0; int mid = l + r >> 1;
        if (L <= mid) ret = (ret + qrysum(lson, l, mid)) % P;
        if (mid + 1 <= R) ret = (ret + qrysum(rson, mid + 1, r)) % P;
        return ret;
    }

    void rangeplus(int rt, int l, int r)
    {
        down(rt, l, r); //先下传标记
        if (L <= l && r <= R) { inc[rt] = (inc[rt] + k) % P; return; } //区间更新,inc加上k
        int mid = l + r >> 1;
        if (L <= mid) rangeplus(lson, l, mid);
        if (mid + 1 <= R) rangeplus(rson, mid + 1, r);
        down(lson, l, mid), down(rson, mid + 1, r); //下传儿子的标记
        sum[rt] = (sum[lson] + sum[rson]) % P;
    }

    void rangemtp(int rt, int l, int r)
    {
        down(rt, l, r); //先下传标记
        if (L <= l && r <= R) { mtp[rt] = mtp[rt] * k % P, inc[rt] = inc[rt] * k % P; return; } //区间更新,mtp和inc都乘以一个k
        int mid = l + r >> 1;
        if (L <= mid) rangemtp(lson, l, mid);
        if (mid + 1 <= R) rangemtp(rson, mid + 1, r);
        down(lson, l, mid), down(rson, mid + 1, r); //下传儿子的标记
        sum[rt] = (sum[lson] + sum[rson]) % P;
    }
} tree;

inline ll read() //读入加速
{
    ll x = 0, f = 0;
    char c = getchar();
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
    for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + c - '0';
    return f ? -x : x;
}

void init()
{
    n = read(), P = read();
    for (int i = 1; i <= n; i++) *(a + i) = read();
    tree.build(1, 1, n);
}

void solve()
{
    q = read();
    while (q--)
    {
        op = read();
        if (op == 1)
            L = read(), R = read(), k = read(),
            tree.rangemtp(1, 1, n);
        else if (op == 2)
            L = read(), R = read(), k = read(),
            tree.rangeplus(1, 1, n);
        else
            L = read(), R = read(),
            printf("%lld\n", tree.qrysum(1, 1, n));
    }
}

int main()
{
    init();
    solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/er111er/article/details/79049366
今日推荐