洛谷 3373 【模板】线段树 2

题目描述

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

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)

先放乘再放加

放乘的时候加标记也乘

多开一个bool数组记录有没有乘标记,避免乘0不下传

#include<cstdio>
#define N 100001
using namespace std;
typedef long long LL;
int n,m;
int op,opl,opr;
LL sum[N<<2],fadd[N<<2],fmul[N<<2],w,p,ans;
bool mul[N<<2];
int mid[N<<2];
void build(int k,int l,int r)
{
    fmul[k]=1;
    if(l==r)
    {
        scanf("%d",&sum[k]);
        return;
    }
    mid[k]=l+r>>1;
    build(k<<1,l,mid[k]);
    build(k<<1|1,mid[k]+1,r);
    sum[k]=(sum[k<<1]+sum[k<<1|1])%p;
}
void down(int k,int l,int mid,int r)
{
    if(mul[k])
    {
        sum[k<<1]=sum[k<<1]*fmul[k]%p;
        sum[k<<1|1]=sum[k<<1|1]*fmul[k]%p;
        fadd[k<<1]=fadd[k<<1]*fmul[k]%p;
        fadd[k<<1|1]=fadd[k<<1|1]*fmul[k]%p;
        fmul[k<<1]=fmul[k<<1]*fmul[k]%p;
        fmul[k<<1|1]=fmul[k<<1|1]*fmul[k]%p;
        mul[k<<1]=mul[k<<1|1]=true;
        fmul[k]=1; mul[k]=false;
    }
    if(fadd[k])
    {
        sum[k<<1]=(sum[k<<1]+fadd[k]*(mid-l+1)%p)%p;
        sum[k<<1|1]=(sum[k<<1|1]+fadd[k]*(r-mid)%p)%p;
        fadd[k<<1]=(fadd[k<<1]+fadd[k])%p;
        fadd[k<<1|1]=(fadd[k<<1|1]+fadd[k])%p;
        fadd[k]=0;
    }
}
void solve(int k,int l,int r)
{
    if(l>=opl && r<=opr)
    {
        if(op==1)
        {
            sum[k]=sum[k]*w%p;
            fadd[k]=fadd[k]*w%p;
            fmul[k]=fmul[k]*w%p;
            mul[k]=true;
        }
        else if(op==2)
        {
            sum[k]=(sum[k]+w*(r-l+1)%p)%p;
            fadd[k]+=w;
        }
        else ans=(ans+sum[k])%p;
        return;
    }
    if(mul[k] || fadd[k]) down(k,l,mid[k],r);
    if(opl<=mid[k]) solve(k<<1,l,mid[k]);
    if(opr>mid[k]) solve(k<<1|1,mid[k]+1,r);
    sum[k]=(sum[k<<1]+sum[k<<1|1])%p;
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    build(1,1,n);
    while(m--)
    {
        scanf("%d",&op);
        if(op==1 || op==2)
        {
            scanf("%d%d%d",&opl,&opr,&w);
            solve(1,1,n);
        }
        else
        {
            ans=0;
            scanf("%d%d",&opl,&opr);
            solve(1,1,n);
            printf("%lld\n",ans);
        }
    }
}
时间: 2024-10-29 10:46:25

洛谷 3373 【模板】线段树 2的相关文章

[洛谷3373]【模板】线段树 2

思路: 线段树.同时维护两个 lazy tag ,一个维护乘,一个维护加.根据加法结合律,可以得出:当同一个结点进行两次加操作时,新的标记等于两次标记之和.根据乘法结合律,可以得出:当同一个结点进行两次乘操作时,新的标记等于两次标记之积.根据乘法分配律,可以得出:当同一个结点先进行了加操作,再进行乘操作时,两个标记都要乘以新乘上的值. 1 #include<cstdio> 2 #include<cctype> 3 #include<cstring> 4 #include

线段树_区间加乘(洛谷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

AC自动机(附洛谷P3769模板题)

首先,介绍一下AC自动机(Aho-Corasick automaton),是一种在一个文本串中寻找每一个已给出的模式串的高效算法. 在学习AC自动机之前,你需要先学习Trie树和KMP算法,因为AC自动机正式利用并结合了两者的思想. 说到实际的不同,其实AC自动机只是在Trie树上引入了一个类似KMP中next数组的东西叫做Fail指针. 对于每一个节点,Fail指针指向该节点所代表的字符串中,次长的.在Trie树中存在的后缀(因为最长的在Trie树种存在的后缀就是其本身)所代表的节点. 举例:

算法模板——线段树1(区间加法+区间求和)

实现功能——1:区间加法:2:区间求和 最基础最经典的线段树模板.由于这里面操作无顺序之分,所以不需要向下pushup,直接累积即可 1 var 2 i,j,k,l,m,n,a1,a2,a3,a4:longint; 3 a,b:array[0..100000] of longint; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end; 8 function min

算法模板——线段树5(区间开根+区间求和)

实现功能——1:区间开根:2:区间求和(此模板以BZOJ3038为例) 作为一个非常规的线段树操作,其tag也比较特殊呵呵哒 1 var 2 i,j,k,l,m,n:longint; 3 a,b:array[0..500000] of int64; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end; 8 function min(x,y:longint):long

【C++】最近公共祖先LCA(Tarjan离线算法)&amp;&amp; 洛谷P3379LCA模板

1.前言 首先我们介绍的算法是LCA问题中的离线算法-Tarjan算法,该算法采用DFS+并查集,再看此算法之前首先你得知道并查集(尽管我相信你如果知道这个的话肯定是知道并查集的),Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解(个人认为). 2.思想 下面详细介绍一下Tarjan算法的思想: 1.任选一个点为根节点,从根节点开始. 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过. 3.若是v还有子节点,返回2,否则下一步. 4.合并v到u上. 5.寻找与当前点

【线段树】【P3372】模板-线段树

百度百科 Definition&Solution 线段树是一种log级别的树形结构,可以处理区间修改以及区间查询问题.期望情况下,复杂度为O(nlogn). 核心思想见百度百科,线段树即将每个线段分成左右两个线段做左右子树.一个线段没有子树,当且仅当线段表示的区间为[a,a]. 由于编号为k的节点的子节点为2k以及2k+1,线段树可以快速的递归左右叶节点. lazy标记:当进行区间修改的时候,如果一个区间整体全部被包含于要修改的区间,则可以将该区间的值修改后,将lazy标记打在区间上,不再递归左

CSU-ACM集训-模板-线段树进阶

A题 原CF 438D The Child and Sequence 题意 给一串数字,m次操作,1.区间查询:2.区间取模:3.单点修改 基本思路 考虑到模如果大于区间的最大值,则取模没有意义.若小于则向下查询并修改,考虑到一个数每次取模最多为原数的\(1/2\),则可认为修改次数不超过\(\log{2}{n}\) 时间复杂度为\(O(n\log{2}{n}\log{2}{n})\) #include<bits/stdc++.h> #define FOR(i,a,b) for(int i=a

模板 - 线段树

线段树还需要模板的菜鸡 #include<bits/stdc++.h> using namespace std; typedef long long ll; #define lt ls, l, m #define rt rs, m + 1, r #define ls (o<<1) #define rs (o<<1|1) const int MAXM = 100000 + 5; ll a[MAXM]; ll st[MAXM * 4], lazy[MAXM * 4]; in

算法模板——线段树4(区间加+区间乘+区间覆盖值+区间求和)

实现功能——1:区间加法 2:区间乘法 3:区间覆盖值 4:区间求和 这是个四种常见线段树功能的集合版哦...么么哒(其实只要协调好三种tag的关系并不算太难——前提是想明白了线段树的工作模式) 代码长度几经修改后也大为缩水 还有!!!——通过BZOJ1798反复的尝试,我的出来一个重要结论——尽量减少pushup操作的不必要使用次数,对于程序提速有明显的效果!!! 1 type vet=record 2 a0,a1:longint; 3 end; 4 var 5 i,j,k,l,m,n,a1,