【题解】维护序列(线段树)
先看题面:
题目背景
老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
题目描述
有一个长为 n 的数列 {an},有如下三种操作形式:
- 格式
1 t g c
,表示把所有满足t<=i<=g改为 ai*c; - 格式
2 t g c
表示把所有满足t<=i<=g的 a_i 改为 a_i+c; - 格式
3 t g
询问所有满足 t<=i<=g的 a_i 的和模 P 的值。
输入格式
第一行两个整数 n 和 p。
第二行含有 n 个非负整数,表示数列 {ai} 。
第三行有一个整数 m,表示操作总数。
从第四行开始每行描述一个操作,同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
输出格式
对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
输入输出样例
输入
7 43 1 2 3 4 5 6 7 5 1 2 5 5 3 2 4 2 3 7 9 3 1 3 3 4 7
输出
2 35 8
说明/提示
样例输入输出解释
- 初始时数列为 {1,2,3,4,5,6,7}。
- 经过第 1 次操作后,数列为 {1,10,15,20,25,6,7}。
- 对第 2 次操作,和为 10+15+20=45,模 43 的结果是 2。
- 经过第 3 次操作后,数列为 {1,10,24,29,34,15,16}。
- 对第 4 次操作,和为 1+10+24=35,模 43 的结果是 35。
- 对第 5 次操作,和为 29+34+15+16=94,模 43 的结果是8。
对于全部的测试点,1<=t<=g<=n,0<=c,ai<=10^9,1<=P<=10^9,n<=100 000。
正片
前置芝士:线段树
首先需要确定数据范围,这里使用线段树的话,需要四个数组:存原始数据的a数组,线段树t数组,进行修改的两个lazy数组(分别用于对加法和乘法的操作)add,vx。
通过题目给的数据范围可以知道本题需要开long long (我就是因为少开了两个long long 改了半天QAQ)
我认为本题的特殊点在加法和乘法的同时实现上。我们知道,使用懒标记对线段树进行区间修改关键在于标记的下传,即pushdown操作。所以在有乘法和加法同时存在的情况下,我们在进行下传操作时就要注意它们的顺序问题,即按照运算顺序(乘法的优先级比加法高)进行下传,如果不注意顺序的话很容易就会导致错误。(本蒟蒻也是在这里想了好长时间QAQ)
这里想通之后代码就很好写啦!
代码如下,其他小细节请看注释~
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const int M=1e5+5; 5 int n,m,p; 6 int c[M];//储存原始数据用来建树 7 ll add[4*M],vx[4*M],t[4*M];//分别表示加法、乘法和线段树 8 inline int read(){//快读 9 int x=0,f=1; 10 char ch=getchar(); 11 while(ch<'0'||ch>'9'){ 12 if(ch=='-') 13 f=-1; 14 ch=getchar(); 15 } 16 while(ch>='0'&&ch<='9'){ 17 x=(x<<1)+(x<<3)+(ch^48); 18 ch=getchar(); 19 } 20 return x*f; 21 } 22 inline void build(int k,int l,int r)//此处建树 23 { 24 vx[k]=1; 25 add[k]=0;//可以在建树的时候初始化加法和乘法数组哦~ 26 if(l==r){ 27 t[k]=c[l]; 28 return; 29 } 30 int mid=(l+r)>>1; 31 build(k<<1,l,mid); 32 build(k<<1|1,mid+1,r); 33 t[k]=t[k<<1]+t[k<<1|1]; 34 t[k]%=p;//加法和乘法都不影响取模操作,这里可以直接取模方便计算 35 return; 36 } 37 void pushdown(int k,int l,int r,int mid)//【核心部分】:pushdown下传操作 38 { 39 t[k<<1]=(t[k<<1]*vx[k]+(mid-l+1)*add[k])%p;//处处模(大雾) 40 t[k<<1|1]=(t[k<<1|1]*vx[k]+(r-mid)*add[k])%p;//先修改儿子节点的值 41 vx[k<<1]=(vx[k<<1]*vx[k])%p; 42 vx[k<<1|1]=(vx[k<<1|1]*vx[k])%p;//乘法在前~ 43 add[k<<1]=(add[k<<1]*vx[k]+add[k])%p; 44 add[k<<1|1]=(add[k<<1|1]*vx[k]+add[k])%p;//加法在后~ 45 vx[k]=1; 46 add[k]=0;//恢复父亲节点的标记 47 return; 48 } 49 inline void multiply(int k,int l,int r,int a,int b,int w)//乘法操作 50 { 51 if(l>b||r<a) return; 52 if(l>=a&&r<=b){ 53 t[k]=(t[k]*w)%p; 54 vx[k]=(vx[k]*w)%p; 55 add[k]=(add[k]*w)%p;//优先级问题,算乘法时要算加法,算加法时不算乘法 56 return; 57 } 58 int mid=(l+r)>>1; 59 pushdown(k,l,r,mid); 60 multiply(k<<1,l,mid,a,b,w); 61 multiply(k<<1|1,mid+1,r,a,b,w); 62 t[k]=(t[k<<1]+t[k<<1|1])%p;//疯狂取模(狗头) 63 return; 64 } 65 inline void update(int k,int l,int r,int a,int b,int w)//加法操作 66 { 67 if(l>b||r<a) return; 68 if(l>=a&&r<=b){ 69 add[k]=(add[k]+w)%p; 70 t[k]=(t[k]+w*(r-l+1))%p;//修改加法标记可以不算乘法 71 return; 72 } 73 int mid=(l+r)>>1; 74 pushdown(k,l,r,mid); 75 update(k<<1,l,mid,a,b,w); 76 update(k<<1|1,mid+1,r,a,b,w); 77 t[k]=(t[k<<1]+t[k<<1|1])%p; 78 return; 79 } 80 inline ll search(int k,int l,int r,int x,int y)//查找操作,别忘了开long long! 81 { 82 if(y<l||x>r) return 0; 83 if(x<=l&&y>=r) return t[k]; 84 ll ans=0; 85 int mid=(l+r)>>1; 86 pushdown(k,l,r,mid); 87 ans+=search(k<<1,l,mid,x,y); 88 ans+=search(k<<1|1,mid+1,r,x,y); 89 return ans%p; 90 } 91 int main() 92 { 93 n=read();p=read(); 94 for(int i=1;i<=n;i++) 95 c[i]=read();//简单易懂的读入 96 build(1,1,n);//清晰明了的建树 97 m=read(); 98 int a,b,x; 99 int f; 100 while(m--){//进行操作 101 f=read();a=read();b=read(); 102 if(f==1){ 103 x=read(); 104 multiply(1,1,n,a,b,x); 105 } 106 else if(f==2){ 107 x=read(); 108 update(1,1,n,a,b,x); 109 } 110 else if(f==3) printf("%lld\n",search(1,1,n,a,b)%p);//printf中的“%lld”别写错了 111 } 112 return 0; 113 }
完美结束~
注:蒟蒻第一次写题解,写的不好还请见谅orz