BZOJ 1798: [Ahoi2009]Seq 维护序列seq (线段树乘法加法的混合操作)



题目:点击打开链接

大意:一个数组,三个操作,第一种是区间[a,b]每个数乘乘,第二种是区间[a,b]每个数加c,第三种是查询[a,b]区间的和并对p取摸。

两种操作就不能简单的只往下传标记。每次传乘法标记时,要把加法标记同时乘上乘法标记,例如某个区间先进来一个加法标记add,之后又进来一个乘法标记mul。

那么结果为(x + add) * mul = x * mul + add * mul。这样向下传标记的时候就相对独立。递归边界更新加法标记之前先乘上该节点的mul,左右儿子down的时候先将儿子的add乘上本节点的mul。

最后说一下sum,比如本节点的存在加法标记x和乘法标记y,并且是先加上x,再乘上y,则左儿子的sum要更新为(sum+x)*y。由于乘法标记传到本节点的时候更新了加法标记,x = x*y,所以sum[o<<1] = (左区间的长度*x) + sum[o<<1]*y。

/**************************************************************
    Problem: 1798
    User: __ElemenT
    Language: C++
    Result: Accepted
    Time:4676 ms
    Memory:10184 kb
****************************************************************/

#include <cstdio>
#define lson o<<1, l, m
#define rson o<<1|1, m+1, r
typedef long long LL;
const int maxn = 100005;

int n, a, b, c, k, q;
LL sum[maxn<<2], add[maxn<<2], mul[maxn<<2], p;

void up(int o) {
    sum[o] = (sum[o<<1]+sum[o<<1|1]) %p;
}

void build(int o, int l, int r) {
    add[o] = 0, mul[o] = 1;
    if(l == r) {
        scanf("%lld", &sum[o]);
        return;
    }
    int m = (l+r) >> 1;
    build(lson);
    build(rson);
    up(o);
}

void down(int o, int len) {
   // if(add[o] != 0 && mul[o] != 1) {
        add[o<<1] = (add[o<<1] * mul[o] + add[o]) %p;
        add[o<<1|1] = (add[o<<1|1] * mul[o] + add[o]) %p;
        mul[o<<1] = mul[o<<1] * mul[o] %p;
        mul[o<<1|1] = mul[o<<1|1] * mul[o] %p;
        sum[o<<1] = (sum[o<<1] * mul[o] + add[o] * (len-(len>>1))) %p;
        sum[o<<1|1] = (sum[o<<1|1] * mul[o] + add[o] * (len>>1)) %p;
        add[o] = 0, mul[o] = 1;
   // }
}

void update(int o, int l, int r, int op) {
    if(a <= l && r <= b) {
        if(op == 1) {
            add[o] = add[o]*c %p;
            mul[o] = mul[o]*c %p;
            sum[o] = sum[o]*c %p;
        } else {
            add[o] = (add[o] + c) %p;
            sum[o] = (sum[o] + (LL)c*(r-l+1)) %p;
        }
        return;
    }
    down(o, r-l+1);
    int m = (l+r) >> 1;
    if(a <= m) update(lson, op);
    if(m < b ) update(rson, op);
    up(o);
}

LL query(int o, int l, int r) {
    if(a <= l && r <= b) return sum[o] %p;
    down(o, r-l+1);
    int m = (l+r) >> 1;
    LL ans = 0;
    if(a <= m) ans = query(lson);
    if(m < b ) ans += query(rson);
    return ans %p;
}

int main()
{
    scanf("%d%lld", &n, &p);
    build(1, 1, n);
    scanf("%d", &q);
    while(q--) {
        scanf("%d%d%d", &k, &a, &b);
        if(k != 3) {
            scanf("%d", &c);
            update(1, 1, n, k);
        } else printf("%lld\n", query(1, 1, n));
    }
    return 0;
}
时间: 2024-10-09 14:23:05

BZOJ 1798: [Ahoi2009]Seq 维护序列seq (线段树乘法加法的混合操作)的相关文章

BZOJ 1798 Ahoi 2009 维护序列seq

题目大意:维护一个序列,能够区间加,区间乘,然后取区间和模一个数的值. 思路:线段树维护一个有两个域的标记,一个表示加,一个表示乘.下传的时候一起下传,先乘后加. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 100010 #define MO p #define LEFT (pos << 1) #de

bzoj 1798: [Ahoi2009]Seq 维护序列seq 线段树 区间乘法区间加法 区间求和

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/problem.php?id=1798 Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列

1798: [Ahoi2009]Seq 维护序列seq

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MBSubmit: 5886  Solved: 2087[Submit][Status][Discuss] Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和

bzoj1798: [Ahoi2009]Seq 维护序列seq(线段树多重标记下传)

www.cnblogs.com/shaokele/ bzoj1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec Memory Limit: 64 MB Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,-,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模P的值

bzoj1798: [Ahoi2009]Seq 维护序列seq 2011-12-20

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MB Submit: 497  Solved: 203 [Submit][Status][Discuss] Description 老 师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的

bzoj1798[Ahoi2009]Seq 维护序列seq

bzoj1798[Ahoi2009]Seq 维护序列seq 题意: 维护序列,支持区间加.区间乘.区间求和模一个数.序列大小和操作数≤100000 题解: 线段树,加标记和乘标记的处理同bzoj4003.模的时候注意细节. 代码: 1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define inc(i,j,k) for(int i=j;i<=k;i++) 5 #define m

bzoj 1798 [Ahoi2009]Seq 维护序列seq(线段树+传标)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1798 [题意] 给定一个序列,要求提供区间乘/加,以及区间求和的操作 [思路] 线段树+传标. 下传标记的方式可以类比这里 click here [代码] 1 #include<set> 2 #include<cmath> 3 #include<queue> 4 #include<vector> 5 #include<cstdio>

BZOJ 1798 [Ahoi2009]Seq 维护序列seq 线段树

题意:链接 方法:线段树 解析: 俩标记sb题 更新乘的时候更新加 完了 代码: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define N 100010 using namespace std; typedef

【BZOJ 1798】 [Ahoi2009]Seq 维护序列seq

Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值; (3)询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模P的值. Input 第一行两个整数N和P(1≤P≤1000000000).第二行含有N个非负整数,从左到右依次为a1,a2,…,aN, (0≤ai≤1000000000,1≤i≤N).第三行有一个整