[洛谷P2023, AHOI2009]维护序列

这是一道典型的线段树板子题. hzwer说过, 对于线段树的多标记 lazy-tag 可以根据标记的优先级, 优先级高的先下传.

但是我一直思路有点问题, 比如说这道题.

这道题要求的是区间加法, 区间乘法, 要查询的是区间和.

我开始的想法是, 不管是加法标记还是乘法标记都是相斥的, 要进行加法, 就必须把乘法标记清零(其实是清一),

要进行乘法, 就必须把加法标记清零.

这样想的话, 无法在均摊logn的时间下, 完成RMQ. 期间还想了加法标记使用标记永久化, 乘法标记使用延迟下传.

后来自己写了个代码框架, 就看了别人的题解. 观后发现确实是乘法的优先级要比加法的高.

主要操作大致是这样的, 对于加法操作, 对包含的区间直接把加法标记加上即可.

对于乘法操作, 对包含的区间的乘法标记和加法标记都要乘上新的乘法标记. 可能代码更加直观.

比如说 left 左儿子的标记为 * a + b, 此时传递一个mutiTag c, 那么 left 的标记就会变成 * a * c + b * c.

还有就是, 对于幺元, 加法是零, 乘法是一.

在 build函数中, 要把所有区间的addTag都设为 0 , mutiTag都设为 1 .

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
const int MAXN = 1e5 + 600;
typedef long long ll;
using namespace std;
int n, m;
int cmd, ql, qh;
ll v, MOD;
struct Segment{
    ll sum, addTag, mutiTag;
} s[MAXN << 2 | 1];
inline void spread(int o, int lo, int hi){
    int left = o << 1, right = o << 1 | 1;
    if(s[o].mutiTag != 1){// 先传递 mutiTag
        s[left].sum = (s[left].sum * s[o].mutiTag) % MOD;
        s[right].sum = (s[right].sum * s[o].mutiTag) % MOD;

        s[left].mutiTag = (s[left].mutiTag * s[o].mutiTag) % MOD;
        s[right].mutiTag = (s[right].mutiTag * s[o].mutiTag) % MOD;

        s[left].addTag = (s[left].addTag * s[o].mutiTag) % MOD;
        s[right].addTag = (s[right].addTag * s[o].mutiTag) % MOD;

        s[o].mutiTag = 1;
    }
    if(s[o].addTag){// 再传递 addTag
        int mi = (lo + hi) >> 1;
        int leftSize = mi - lo, rightSize = hi - mi;

        s[left].sum = (s[left].sum + (leftSize * s[o].addTag)) % MOD;
        s[right].sum = (s[right].sum + (rightSize * s[o].addTag)) % MOD;

        s[left].addTag = (s[left].addTag + s[o].addTag) % MOD;
        s[right].addTag = (s[right].addTag + s[o].addTag) % MOD;

        s[o].addTag = 0;
    }
}
inline void update(int o){
    s[o].sum = (s[o << 1].sum + s[o << 1 | 1].sum) % MOD;
}
void build(int o, int lo, int hi){
    if(hi - lo < 2){
        scanf("%lld", &s[o].sum);
        s[o].sum = s[o].sum % MOD;

        return;
    }
    int mi = (lo + hi) >> 1;
    build(o << 1, lo, mi);
    build(o << 1 | 1, mi, hi);

    update(o);
    s[o].addTag = 0;
    s[o].mutiTag = 1;
}
void rangeAdd(int o, int lo, int hi){
    if(qh <= lo || hi <= ql){// 完全不在查询区间的, 最先判断.
        return;
    }
    if(ql <= lo && hi <= qh){
        s[o].addTag += v;
        s[o].sum += (v * (hi - lo)) % MOD;
        s[o].sum %= MOD;
        return;
    }

    spread(o, lo, hi);
    int mi = (lo + hi) >> 1;
    rangeAdd(o << 1, lo, mi);
    rangeAdd(o << 1 | 1, mi, hi);

    update(o);
}
void rangeMuti(int o, int lo, int hi){
    if(qh <= lo || hi <= ql){
        return;
    }
    if(ql <= lo && hi <= qh){
        // spread(o, lo, hi);
        s[o].mutiTag = (s[o].mutiTag * v) % MOD;
        s[o].addTag = (s[o].addTag * v) % MOD;
        s[o].sum = (s[o].sum * v) % MOD;
        return;
    }

    spread(o, lo, hi);
    int mi = (lo + hi) >> 1;
    rangeMuti(o << 1, lo, mi);
    rangeMuti(o << 1 | 1, mi, hi);

    update(o);
}
ll query(int o, int lo, int hi){
    if(qh <= lo || hi <= ql){
        return 0;
    }
    if(ql <= lo && hi <= qh){
        return s[o].sum;
    }

    spread(o, lo, hi);
    int mi = (lo + hi) >> 1;
    ll rst = 0;
    rst = (query(o << 1, lo, mi) + query(o << 1 | 1, mi, hi)) % MOD;
    return rst;
}
void print(int o, int lo, int hi){
    if(hi - lo < 2){
        printf(" %d-%lld", lo, s[o].sum);
        return;
    }
    spread(o, lo, hi);
    int mi = (lo + hi) >> 1;
    print(o << 1, lo, mi);
    print(o << 1 | 1, mi, hi);
}
int main(){
    scanf("%d%lld", &n, &MOD);
    build(1, 1, ++n);
    // print(1, 1, n), puts("\tTEST\n");// print调试, 非常好用.
    scanf("%d", &m);
    for(int i = 0; i < m; ++i){
        scanf("%d%d%d", &cmd, &ql, &qh);
        ++qh;
        switch(cmd){
            case 1:
                scanf("%lld", &v);
                rangeMuti(1, 1, n);
                break;
            case 2:
                scanf("%lld", &v);
                rangeAdd(1, 1, n);
                break;
            case 3:
                printf("%lld\n", query(1, 1, n));
                break;
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值