Luogu 3373 - 【模板】线段树 2 - [加乘线段树]

题目链接:https://www.luogu.org/problemnew/show/P3373

题目描述

如题,已知一个数列,你需要进行下面三种操作:

1.将某区间每一个数乘上x

2.将某区间每一个数加上x

3.求出某区间每一个数的和

输入格式:

第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k

操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k

操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果

输出格式: 

输出包含若干行整数,即为所有操作3的结果。

输入样例#1: 

5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4

输出样例#1: 

17
2

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

(数据已经过加强^_^)

样例说明:

故输出应为17、2(40 mod 38=2)

题解:

由原本的单个的lazy标记变成两个标记:add标记和mul标记,一个记录加,一个记录乘;

需要注意的是,如果同时存在add标记和mul标记,应当先更新乘法标记,再更新加法标记,

而且,当一个节点要乘以 x 时,如果它的add标记还存在,记得要把add标记也乘 x(具体见代码的Update_Mul成员函数)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100000+10;

ll MOD;
int n,m;
ll a[maxn];

/********************************* Segment Tree - st *********************************/
struct Node
{
    int l,r;
    ll val;
    ll add,mul;
    void Update_Mul(ll x)
    {
        val=(val*x)%MOD;
        mul=(mul*x)%MOD;
        add=(add*x)%MOD;
    }
    void Update_Add(ll x)
    {
        val=(val+(r-l+1)*x)%MOD;
        add=(add+x)%MOD;
    }
}node[4*maxn];
void Pushdown(int root)
{
    int ls=root*2, rs=root*2+1;
    if(node[root].mul!=1)
    {
        node[ls].Update_Mul(node[root].mul);
        node[rs].Update_Mul(node[root].mul);
        node[root].mul=1;
    }
    if(node[root].add)
    {
        node[ls].Update_Add(node[root].add);
        node[rs].Update_Add(node[root].add);
        node[root].add=0;
    }
}
void Pushup(int root)
{
    node[root].val=(node[root*2].val+node[root*2+1].val)%MOD;
}
void Build(int root,int l,int r) //对区间[l,r]建树
{
    if(l>r) return;
    node[root].l=l; node[root].r=r;
    node[root].val=0;
    node[root].add=0;
    node[root].mul=1;
    if(l==r) node[root].val=a[l];
    else
    {
        int mid=l+(r-l)/2;
        Build(root*2,l,mid);
        Build(root*2+1,mid+1,r);
        Pushup(root);
    }
}
void Add(int root,int st,int ed,ll val) //区间[st,ed]全部加上val
{
    if(st>node[root].r || ed<node[root].l) return;
    if(st<=node[root].l && node[root].r<=ed) node[root].Update_Add(val);
    else
    {
        Pushdown(root);
        Add(root*2,st,ed,val);
        Add(root*2+1,st,ed,val);
        Pushup(root);
    }
}
void Mul(int root,int st,int ed,ll val) //区间[st,ed]全部加上val
{
    if(st>node[root].r || ed<node[root].l) return;
    if(st<=node[root].l && node[root].r<=ed) node[root].Update_Mul(val);
    else
    {
        Pushdown(root);
        Mul(root*2,st,ed,val);
        Mul(root*2+1,st,ed,val);
        Pushup(root);
    }
}
ll Query(int root,int st,int ed) //查询区间[st,ed]的和
{
    if(st>node[root].r || ed<node[root].l) return 0;
    if(st<=node[root].l && node[root].r<=ed) return node[root].val;
    else
    {
        Pushdown(root);
        ll ls=Query(root*2,st,ed);
        ll rs=Query(root*2+1,st,ed);
        Pushup(root);
        return (ls+rs)%MOD;
    }
}
/********************************* Segment Tree - st *********************************/

int main()
{
    cin>>n>>m>>MOD;
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    Build(1,1,n);

    for(int i=1;i<=m;i++)
    {
        int op; scanf("%d",&op);
        if(op==1)
        {
            int x,y; ll k;
            scanf("%d%d%lld",&x,&y,&k);
            Mul(1,x,y,k);
        }
        if(op==2)
        {
            int x,y; ll k;
            scanf("%d%d%lld",&x,&y,&k);
            Add(1,x,y,k);
        }
        if(op==3)
        {
            int l,r; scanf("%d%d",&l,&r);
            printf("%lld\n",Query(1,l,r));
        }
    }
}

原文地址:https://www.cnblogs.com/dilthey/p/9446571.html

时间: 2024-08-02 11:18:41

Luogu 3373 - 【模板】线段树 2 - [加乘线段树]的相关文章

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #inc

[luogu P3384] [模板]树链剖分

[luogu P3384] [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数

洛谷P3380 【模板】二逼平衡树(树套树,树状数组,线段树)

洛谷题目传送门 emm...题目名写了个平衡树,但是这道题的理论复杂度最优解应该还是树状数组套值域线段树吧. 就像dynamic ranking那样(蒟蒻的Sol,放一个link骗访问量233) 所有的值(包括初始a数组,操作1.3.4.5的k)全部先丢进去离散化 对于1操作查比它小的数,挑log棵线段树,找区间小于这个数的个数+1,这个还比较好像 操作2就是dynamic ranking,log棵线段树一起加减,像静态主席树求第k小一样跳,操作3 dynamic ranking里也有 操作4先

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

Solution: 题解 CF896C Willem, Chtholly and Seniorious(线段树解珂朵莉树)

Intro: 珂朵莉树模板题 怎么所有题解都是珂朵莉树啊啊啊啊 于是本蒟蒻决定来一发中(feng)规(kuang)中(luan)矩(gao)的线段树 首先这棵线段树只维护懒标记 来一发定义 线段树节点\(u\)维护区间\([l_u,r_u]\)的内容 懒标记\(t_u\):当\(t_u\not=0\)时表示区间\([l_u,r_u]\)全是\(t_u\),\(t_u=0\)就是没有懒标记 建立线段树 在建立时顺便处理\(l_u,r_u\),只要当\(l_u=r_u\)时就打上标记 P.s \(L

bzoj 3626 [LNOI2014]LCA(离线处理+树链剖分,线段树)

3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1272  Solved: 451[Submit][Status][Discuss] Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1. 设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先. 有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[

3110: [Zjoi2013]K大数查询 树状数组套线段树

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1384  Solved: 629[Submit][Status] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a b c或2 a b

树状数组和线段树的那些事

共同点:线段树,树状数组都是用来快速搜索. 线段树通过分支查找,树状数组通过用二进制快速查找,树状数组的查询和更新时间复杂度都是O(logN),通常来说,树状数组能做的线段树都能解决.线段树的范围更广. 但是树状数组的范围虽然小,但是执行效率却比线段树高了不少. 树状数组在处理二维,三维的搜索时就会很方便. 比如VJ树状数组专题的C和K题分别是二维和三维.但是代码区别都不大,基本就是套模板. 本人觉得树状数组在处理逆序数和逆序对,区间子集的问题方面树状数组更有优势(可能是我题目写少了...) 但

树状数组与线段树

一:树状数组 树状数组是对一个数组改变某个元素和求和比较实用的数据结构.两中操作都是O(logn). 需求:有时候我们需要频繁地求数组的前k项和或者求数组从小标i到j的和,这样每次最坏情况下的时间复杂度就会为O(N),这样效率太低了.而树状数组主要就是为了解决这样一个问题.树状数组在求和及修改都可以在O(lgN)时间内完成. 树状数组需要额外维护一个数组,我们设为C[N],原数组为A[N], 其中每个元素C[i]表示A[i-2^k+1]到A[i]的和,这里k是i在二进制时末尾0的个数.注意通过位