「AHOI2009」维护序列题解

「AHOI2009」维护序列题解

文件大小2.9KB是什么啊!!!

题目大意:

对于每个操作:

1 t g c表示把区间[t,g] * c;

2 t g c表示把区间[t,g] + c;

3 t g表示询问[t,g]的区间和模P的值 。

题目思路

对于操作2,就是树状数组的区间加法,重点在操作1,区间乘法怎么求。

建树

众所周知,对于区间加法,我们可以增加一个延迟标记lazy[]用来存储一个区间是否需要修改。类似地,如果我们的线段树有两个修改操作的话,我们可以再添加一个延迟标记lazyc[]表示乘法(加法为lazyj[]),并相应的在建树的时候初始化:

int a[5000005]/*原数组*/, s[5000005]/*线段树*/, lazyj[5000005]/*加法标记*/, lazyc[5000005]/*乘法标记*/;
void build(int k, int l, int r)
{
    lazyc[k] = 1;//乘法要初始化为1
    lazyj[k] = 0;
    if (l == r)
    {
        s[k] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(k << 1, l, mid);
    build(k << 1 | 1, mid + 1, r);
    s[k] = s[k << 1] + s[k << 1 | 1];
}

下传

建树完成之后,我们就需要对应地标记下传。那么问题又来了,对于同一个区间,如果既有加法运算,又进行了乘法运算的话,在进行乘法的运算中,它会影响加法(例如区间[L,R]既加了10,[L-2,R+2]又乘了10,那么加过10的区间[L,R]就会经过两种运算),那么对于lazyc[]lazyj[](加法和乘法)我们应该先下传那一个呢?

对于lazyc[]它只存储乘法,加法与它无关,所以顺序的关键就是lazyj[]!

我们不妨来模拟一下:

如果先下传加法的话,那么lazyj[]就会先加后乘,我们设加上的数为j,乘的数为c。那么下传后的lazyj[k]就会变为\((lazyj[k] + j) * c\),对于没进行加法运算的区间\(lazyj[k]*c\)差值就是\(j-1(lazyj[c])\),而这显然不是我们想要的

如果先下传加法的话,那么lazyj[]就会先乘后加,我们同样设加上的数为j,乘的数为c。那么下传后的lazyj[k]就会变为\(lazyj[k]*c+j\),对于没进行加法运算的区间\(lazyj[k]*c\)的差值就是\(j\),这才是真正的差值

void pushdown(int k, int l, int r)
{
    if (lazyc[k] != 1)//如果lazyc[k]没有进行过标记
    {
        //修改左子树
        s[k << 1] = (s[k << 1] * lazyc[k]) % mod;
        lazyc[k << 1] = (lazyc[k << 1] * lazyc[k]) % mod;
        lazyj[k << 1] = (lazyj[k << 1] * lazyc[k]) % mod;//对于加法的影响
		//修改右子树
        s[k << 1 | 1] = (s[k << 1 | 1] * lazyc[k]) % mod;
        lazyc[k << 1 | 1] = (lazyc[k << 1 | 1] * lazyc[k]) % mod;
        lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] * lazyc[k]) % mod;//对于加法的影响

        lazyc[k] = 1;//当前值归1
    }
    int mid = l + r >> 1;
    if (lazyj[k] != 0)//如果lazyj[k]没有进行过标记
    {
        //修改左子树
        s[k << 1] = (s[k << 1] + (mid - l + 1) * lazyj[k]) % mod;
        lazyj[k << 1] = (lazyj[k << 1] + lazyj[k]) % mod;
		//修改右子树
        s[k << 1 | 1] = (s[k << 1 | 1] + (r - mid) * lazyj[k]) % mod;
        lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] + lazyj[k]) % mod;

        lazyj[k] = 0;//当前值归0
    }
}

更新

然后就是更新操作因为题目右两种修改操作,如果把加法和乘法分别写一个函数的话太麻烦,我们可以在原本的update函数里面添加一个标记f,用于标记当前操作是加法还是乘法。

void update(int k, int l, int r, int x, int y, int v, int f)
{
    if (l > y || r < x)//不在区间内,直接返回
    {
        return;
    }
    if (x <= l && r <= y)
    {
        if (f == 0)//加法
        {
            s[k] = ((s[k] % mod) + (r - l + 1) * (v % mod)) % mod;//因为要对mod取余,所以不能用+=
            lazyj[k] += v;
        }
        else//乘法
        {
            s[k] = ((s[k] % mod) * (v % mod)) % mod;//同理
            lazyc[k] = (lazyc[k] * v) % mod;
            lazyj[k] = (lazyj[k] * v) % mod;//对于加法的修改
        }
        return;
    }
    pushdown(k, l, r);//下传
    int mid = l + r >> 1;
    update(k << 1, l, mid, x, y, v, f);//更新左子树
    update(k << 1 | 1, mid + 1, r, x, y, v, f);//更新右子树
    s[k] = ((s[k << 1] % mod) + (s[k << 1 | 1] % mod)) % mod;//更新当前区间和
}

查询

查询操作与原来没有区别

int query(int k, int l, int r, int x, int y)
{
    if (l > y || r < x)//不在区间内,直接返回0
    {
        return 0;
    }
    if (x <= l && r <= y)//已经覆盖,返回当前值
    {
        return s[k];
    }
    pushdown(k, l, r);//下传
    int mid = l + r >> 1, ans = 0;
    return (query(k << 1, l, mid, x, y) + query(k << 1 | 1, mid + 1, r, x, y)) % mod;//统计左子树和右子树的和
}

CODE

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, mod, T, a[5000005]/*原数组*/, s[5000005]/*树状数组*/, lazyj[5000005]/*加法标记*/, lazyc[5000005]/*乘法标记*/;

void build(int k, int l, int r)
{
    lazyc[k] = 1;
    lazyj[k] = 0;
    //加法初始化为0,乘法为1
    if (l == r)
    {
        s[k] = a[l];
        return;
    }
    int mid = l + r >> 1;
    build(k << 1, l, mid);
    build(k << 1 | 1, mid + 1, r);
    s[k] = s[k << 1] + s[k << 1 | 1];
}

void pushdown(int k, int l, int r)
{
    if (lazyc[k] != 1)//乘法
    {
        //左子树
        s[k << 1] = (s[k << 1] * lazyc[k]) % mod;
        lazyc[k << 1] = (lazyc[k << 1] * lazyc[k]) % mod;
        lazyj[k << 1] = (lazyj[k << 1] * lazyc[k]) % mod;//对应修改加法
		//右子树
        s[k << 1 | 1] = (s[k << 1 | 1] * lazyc[k]) % mod;
        lazyc[k << 1 | 1] = (lazyc[k << 1 | 1] * lazyc[k]) % mod;
        lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] * lazyc[k]) % mod;//对应修改加法
		//当前值归1
        lazyc[k] = 1;
    }
    int mid = l + r >> 1;
    if (lazyj[k] != 0)//加法
    {
        //左子树
        s[k << 1] = (s[k << 1] + (mid - l + 1) * lazyj[k]) % mod;
        lazyj[k << 1] = (lazyj[k << 1] + lazyj[k]) % mod;
		//右子树
        s[k << 1 | 1] = (s[k << 1 | 1] + (r - mid) * lazyj[k]) % mod;
        lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] + lazyj[k]) % mod;
		//当前值归0
        lazyj[k] = 0;
    }
}

void update(int k, int l, int r, int x, int y, int v, int f)
{
    if (l > y || r < x)
    {
        return;
    }
    if (x <= l && r <= y)
    {
        if (f == 0)//加法
        {
            s[k] = ((s[k] % mod) + (r - l + 1) * (v % mod)) % mod;
            lazyj[k] += v;
        }
        else//乘法
        {
            s[k] = ((s[k] % mod) * (v % mod)) % mod;
            lazyc[k] = (lazyc[k] * v) % mod;
            lazyj[k] = (lazyj[k] * v) % mod;
        }
        return;
    }
    pushdown(k, l, r);//下传
    int mid = l + r >> 1;
    update(k << 1, l, mid, x, y, v, f);//更新左子树
    update(k << 1 | 1, mid + 1, r, x, y, v, f);//更新右子树
    s[k] = ((s[k << 1] % mod) + (s[k << 1 | 1] % mod)) % mod;//更新当前区间值和
}

int query(int k, int l, int r, int x, int y)
{
    if (l > y || r < x)
    {
        return 0;
    }
    if (x <= l && r <= y)
    {
        return s[k];
    }
    pushdown(k, l, r);
    int mid = l + r >> 1, ans = 0;
    return (query(k << 1, l, mid, x, y) + query(k << 1 | 1, mid + 1, r, x, y)) % mod;//统计左子树+右子树
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> mod;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    build(1, 1, n);
    cin >> T;
    while (T--)
    {
        int q, t, g, c;
        cin >> q;
        switch (q)
        {
        case 1:
            cin >> t >> g >> c;
            update(1, 1, n, t, g, c, 1);//传1为乘法
            break;
        case 2:
            cin >> t >> g >> c;
            update(1, 1, n, t, g, c, 0);//传0为加法
            break;
        case 3:
            cin >> t >> g;
            cout << query(1, 1, n, t, g) << "\n";//查询区间
            break;
        }
    }

    return 0;
}

posted @ 2023-08-21 10:46  Li_Feiy  阅读(26)  评论(0)    收藏  举报