【题解】维护序列(线段树)

先看题面:

题目背景

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

题目描述

有一个长为 n 的数列 {an},有如下三种操作形式:

  1. 格式 1 t g c,表示把所有满足t<=i<=g改为 ai*c;
  2. 格式 2 t g c 表示把所有满足t<=i<=g的 a_i 改为 a_i+c;
  3. 格式 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

posted @ 2020-08-07 10:03  SingularPoint  阅读(266)  评论(2编辑  收藏  举报