RMQ问题:区间最大值或者最小值问题,类似的还要区间和问题

操作:

(1)求最值 、求和:区间内

(2)修改元素 :点修改、区间修改

线段树:用于区间处理的数据结构,用二叉树构造

二叉折半查找,查找点或者区间的时候:顺着往下查找 。存储空间:4n

修改点:直接修改叶子节点,然后自底向上更新

修改区间:使用lazy标记,加上pushdown函数,更新区间的lazy标记

复杂度:O(nlogn),线段是把n个数按照二叉树进行分组,每次更新有关节点的时候,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数

last cows

第一种做法:用结构体实现线段树

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//线段树做法
/*
从后往前遍历输入的序列,遇到的每个值a表示此牛在剩余牛中排在第a+1个,删除此编号,循环此过程,最终得到的序列即为牛在此队列中的编号序列。
借助线段树查找未删除的数中排在第a+1个位置(编号排序位置)的牛的位置(读取顺序)
*/
struct node{
    int l,r,len;
}cow[100000];
int s[100000],ans[100000];
void build(int v,int l,int r){
    cow[v].l=l;
    cow[v].r=r;
    cow[v].len=r-l+1;
    if(l==r) return;
    int mid=(l+r)/2;
    build(v*2,l,mid);
    build(v*2+1,mid+1,r);
}
int que(int v,int k){
    --cow[v].len;
    if(cow[v].l==cow[v].r) return cow[v].r;
    //找到叶子节点, 注意此处不可用cow[v].len == 0代替,否则单支情况将直接返回,导致未达到最末端
    else if(cow[v*2].len>=k){
        return que(v*2,k);
    }
    else return que(v*2+1,k-cow[v*2].len);////!!!!
}
int main(){
    int n;
    while(~scanf("%d",&n)){
        for(int i=2;i<=n;i++) scanf("%d",&s[i]);
        s[1]=0;
        build(1,1,n);
        for(int i=n;i>=1;i--){
            ans[i]=que(1,s[i]+1);
        }
        for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    }
return 0;
}

第二种做法:完全二叉树(数组)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=11010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//数组实现线段树
int n;
int pre[maxn],tree[maxn*4]={0},ans[maxn]={0};
void build(int n,int last_left){
	for(int i=last_left;i<last_left+n;i++) tree[i]=1; //最后一行赋值
	//从二叉树的最后一行倒推到根节点,根节点的值是牛的总数 
	while(last_left!=1){
		for(int i=last_left/2;i<last_left;i++) tree[i]=tree[i*2]+tree[i*2+1];
		last_left/=2;
	} 
}

int que(int u,int num,int last_left){  //查询+维护,求出当前区间中坐起第num个元素
	tree[u]--;
	if(tree[u]==0&&u>=last_left) return u;
	if(tree[u<<1]<num)   //左子区间数量不够,查到右子区间 
		return que((u<<1)+1,num-tree[u<<1],last_left); 
	if(tree[u<<1]>=num)  //左子区间数量够了 
		return que(u<<1,num,last_left);
}
int main(){
	int las;
	scanf("%d",&n);
	pre[1]=0;
	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
	las=1<<(int(log(n)/log(2))+1);
	//cout<<las<<endl;
	build(n,las);  //从后往前退出每次最后一个数字 
	for(int i=n;i>=1;i--) ans[i]=que(1,pre[i]+1,las)-las+1;
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]); 
return 0;
}

当数据太大:也可以考虑离散化,把原有的大二叉树压缩为小二叉树,但是压缩前后子区间的关系不变

区间修改

操作:(1)加 (2)查询和

lazy_tag方法:当修改一个整块区间时,只对这个线段区间进行整体上的修改,其内部每个元素内容先不修改,只有当这部分线段的一致性被破坏时才把变化之传给子区间(查询时也一样)

tag[]数组:记录节点i是否用到lazy原理,其值是op a b c中的c,如果做了多次lazy,那么add[]可以累加,如果在某次操作中被深入, 破坏了lazy,那么add[]归0

1548:【例 2】A Simple Problem with Integers(线段树的做法)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
int add[maxn*4],a[maxn];
long long summ[maxn*4];
int n,m;
inline int getin(){  //读入优化 
  	char c;
  	int sgn=1;
  	while((c=getchar())<'0'||c>'9') if(c=='-') sgn=-1;
  	int res=c-'0';
  	while((c=getchar())>='0'&&c<='9') res=res*10+c-'0';
  	return res*=sgn;
}
void build(int l,int r,int rt){
	if(l==r){
		summ[rt]=a[l];return;
	}
	int mid=l+r>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	summ[rt]=summ[rt<<1]+summ[rt<<1|1]; //位运算优化常数 
	
}
void adde(int rt,int l,int r,int v){
	add[rt]+=v;
	summ[rt]+=(long long)v*(r-l+1);
}
void pushdown(int rt,int l,int r,int mid){  //标记下方 
	if(add[rt]==0) return;
	adde(rt<<1,l,mid,add[rt]);
	adde(rt<<1|1,mid+1,r,add[rt]);
	add[rt]=0;
}
long long que(int rt,int l,int r,int x,int y){
	if(l>=x&&r<=y) return summ[rt];
	int mid=l+r>>1;
	long long res=0;
	pushdown(rt,l,r,mid);
	if(x<=mid) res+=que(rt<<1,l,mid,x,y);
	if(mid<y) res+=que(rt<<1|1,mid+1,r,x,y);
	return res; 
}
void chan(int rt,int l,int r,int x,int y,int v){
	if(l>=x&&r<=y) {
		return adde(rt,l,r,v);
	}
	int mid=l+r>>1;
	pushdown(rt,l,r,mid);
	if(x<=mid) chan(rt<<1,l,mid,x,y,v);
	if(mid<y) chan(rt<<1|1,mid+1,r,x,y,v);
	summ[rt]=summ[rt<<1]+summ[rt<<1|1];
}

int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,n,1);
    while(m--){
    	int d,l,r,x;
    	scanf("%d ",&d);
    	if(d==1){
    		scanf("%d %d %d",&l,&r,&x);
    		chan(1,1,n,l,r,x);
		}
		else{
			scanf("%d %d",&l,&r);
			printf("%lld\n",que(1,1,n,l,r));
		}
	}
return 0;
}

 

1547:【 例 1】区间和

点修改、区间求和 

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//模板题:点修改、区间查询 
int n,m;
LL summ[maxn*4];
/*
void build(int l,int r,int root){
	summ[root]=0;
	if(l==r) return;
	int mid=(l+r)/2;
	build(1,mid,root*2);
	build(mid+1,r,root*2+1);
	summ[root]=summ[root*2]+summ[root*2+1];
}
*/
LL que(int root,int l,int r,int x,int y){   //调用的时候: upda(1,1,n,a,b) 
	if(r<x||y<l) return 0; //如果要求的区间与找到的区间交集为空,返回 
	if(l>=x&&y>=r) return summ[root];//如果找到的区间包含于要求的区间,返回这个区间的值 
	int mid=(l+r)/2;
	return que(root*2,l,mid,x,y)+que(root*2+1,mid+1,r,x,y);
}
void upda(int root,int l,int r,int a,int b){  //调用的时候: upda(1,1,n,a,b) 
	if(a<l||a>r) return;
	if(l==r&&l==a){    //点修改 
		summ[root]+=b;
		return ;
	}
	int mid=(l+r)/2;
	upda(root*2,l,mid,a,b);
	upda(root*2+1,mid+1,r,a,b);
	summ[root]=summ[root*2]+summ[root*2+1];   //在这里回溯的时候修改 
}
int main(){
	scanf("%d %d",&n,&m);
	int k,a,b;
	///build(1,n,1); //在这里调用建树 
	for(int i=0;i<m;i++){
		scanf("%d %d %d",&k,&a,&b);
		if(k==0) upda(1,1,n,a,b); //点修改,在a上加b 
		else printf("%lld\n",que(1,1,n,a,b));  //区间查询 
	}
return 0;
}

  

1548:【例 2】A Simple Problem with Integers  (树状数组做的)

区间修改(加上x),区间求和

可以用线段树、也可以用树状数组
感觉线段树简单一点,但是不好推
用树状数组讲解:维护两个前缀和
https://blog.csdn.net/gzcszzx/article/details/100539427
维护两个前缀和,
S1[i]=d[i],S2[i]=d[i]*i
查询:位置Pos的前缀和就是(Pos+1)*S1中1到Pos的和 减去 S2中1到Pos的和,[L,R]=SS[R]-SS[L-1]

修改:[L,R]
S1:S1[L]+Tag,S1[R+1]-Tag
S2:S2[L]+Tag*L ,S2[R+1]-Tag*(R+1)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//这道题是模板题:区间求和、区间修改
//可以用线段树、也可以用树状数组
//感觉线段树简单一点,但是不好推 
//用树状数组讲解:维护两个前缀和
//https://blog.csdn.net/gzcszzx/article/details/100539427
/*
维护两个前缀和,
S1[i]=d[i],S2[i]=d[i]*i
查询:位置Pos的前缀和就是(Pos+1)*S1中1到Pos的和 减去 S2中1到Pos的和,[L,R]=SS[R]-SS[L-1]

修改:[L,R]  
S1:S1[L]+Tag,S1[R+1]-Tag   
S2:S2[L]+Tag*L ,S2[R+1]-Tag*(R+1)
*/
LL n,m;
LL a[maxn],d[maxn];  //a[i]为原数组  d[i]为差分数组
LL c1[maxn],c2[maxn];  //两个前缀和
#define lowbit(x) ((x)&(-x)) 
void add(LL x,LL v){
	LL p=x;
	while(x<=n){
		c1[x]+=v;
		c2[x]+=p*v;
		x+=lowbit(x);
	}
}
LL getans(LL x){
	LL ans=0,p=x;
	while(x){
		ans+=(p+1)*c1[x]-c2[x];
		x-=lowbit(x);
	}
	return ans;
}
int main(){
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		d[i]=a[i]-a[i-1];
		add(i,d[i]);
	}
	while(m--){
		int p;
		scanf("%d",&p);
		if(p==1){
			LL l,r,c;
			scanf("%lld %lld %lld",&l,&r,&c);
			add(l,c);
			add(r+1,-c);
		}
		if(p==2){
			LL x,y;
			scanf("%lld %lld",&x,&y);
			printf("%lld\n",getans(y)-getans(x-1));
		}
	}
return 0;
}

  

1549:最大数

 

修改:在序列最后添加数

查询:最后L个数种最大数

单点更新,区间查询

这道题也有两种做法
//但是有一种是单调队列,另一种是线段树
//开始时创建一个大序列,全部设为 2147483647。每插入一个数,就将大序列中空闲部分的第一个数改为被插入的数,然后递归更新上层。复杂度O(nlog2n)。

 原文链接:https://blog.csdn.net/sinat_34943123/article/details/53861325

单调队列的做法:

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=200001; 
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//单调队列的做法
/*
由于先入队的较小数,在有后入队的大数的情况下不可能为答案,所以,可以维护一个单调队列。由于单调队列中入队先后,与数的大小皆是有序的,
故可以用二分查找找到单调队列中,在后l个数里,最靠近队首(最大)的数,即为答案。
ps:(1)线段树常数大故此做法要快得多 (2)c++中可用函数lower_bound实现二分查找功能。
原文链接:https://blog.csdn.net/sinat_34943123/article/details/53861325
*/ 
int a[maxn];   //q是队列 
int q[maxn];  //一个存下标,一个存值
int m,p,num,t; 
int main(){
	scanf("%d %d",&m,&p);
	t=0;
	int tmp,tail=0,l=0;
	char op;
	int xx; 
	for(int i=0;i<m;i++){
		scanf(" %c %d",&op,&xx);
		//cout<<l<<endl;
		if(op=='A'){
			scanf("%d",&xx);
			int shuji=(t+xx)%p;
			while(q[tail]<=shuji&&tail) tail--;
			q[++tail]=shuji;
			a[tail]=++l; 
		}
		if(op=='Q'){
			int pos=lower_bound(a+1,a+1+tail,l-xx+1)-a;
			t=q[pos];
			printf("%d\n",t);
		}
	}
return 0;
}

线段树做法:

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e5+19;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//单点更新,区间查询?
//这道题也有两种做法
//但是有一种是单调队列,另一种是线段树
//开始时创建一个大序列,全部设为 2147483647。每插入一个数,就将大序列中空闲部分的第一个数改为被插入的数,然后递归更新上层。复杂度O(n?log2n)。
int m,p;
int  a[maxn*4];
void build(int root,int l,int r){   //初始化 
	if(l>r) return;
	a[root]=-INF;
	int mid=(l+r)/2;
	if(l<r){  //记得要加这个条件呀。。。。 
		build(root*2,l,mid);
		build(root*2+1,mid+1,r);
	}
}
void upda(int root,int l,int r,int pos,int val){ //在pos位置上增加val值,也就是最后一个位置 
	if(l>r) return;
	if(l==r) a[root]=val;  //找到了根节点,更新
	else{
		int mid=(l+r)/2;
		if(pos<=mid) upda(root*2,l,mid,pos,val);
		else upda(root*2+1,mid+1,r,pos,val);
		a[root]=max(a[root*2],a[root*2+1]);
		//在这里!!!每个节点存储的是最大的孩子节点值 
	} 
}
int que(int root,int l,int r,int x,int y){  //l,r是会变化的 
	if(l>r||l>y||r<x) return -INF;
	if(l>=x&&r<=y) return a[root];
	int mid=(l+r)/2; 
	return max(que(root*2,l,mid,x,y),que(root*2+1,mid+1,r,x,y));
}
int main(){
	scanf("%d %d",&m,&p);
	build(1,1,m);  //最多也只有m个数 
	int num=0;//添加的数的个数
	int t=0; //存储上一次的查找结果 
	//一开始就初始化创建树,共m个节点,因为最多就m个节点
	char op;
	int xx; 
	for(int i=0;i<m;i++){
	//cout<<i<<endl;		
		scanf(" %c %d",&op,&xx);
		//cout<<op<<" "<<xx<<"jj"<<endl;
		if(op=='A'){   //表示添加一个数在后面 
			upda(1,1,m,++num,(xx+t)%p);
		}
		if(op=='Q') { //询问序列最后L个数中最大的数 
			int tmp=que(1,1,m,num-xx+1,num);
			//查询后面xx个数字
			t=tmp;
			printf("%d\n",tmp); 
		}
		getchar();
	}
return 0;
}

  

1550:花神游历各国

//区间修改、区间查询
//并且变化很神奇,l--r中每个国家的喜欢度变为sqrt()

注意要处理节点的值不断sqrt()后的变化,要特判是不是1或者0  mx[root]==1||mx[root]==0

需要数组:mx[maxn*4],summ[maxn*4],num[maxn],分别存储左右孩子最大值、总和、这个节点的值

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//区间修改、区间查询
//并且变化很神奇,l--r中每个国家的喜欢度变为sqrt()
 
int n,m;
LL summ[maxn*4],num[maxn];
LL mx[maxn*4];
void build(int l,int r,int root){
	if(l==r) {
		summ[root]=mx[root]=num[l];  //根节点赋值
		return;
	}
	int mid=(l+r)/2; 
	build(l,mid,root*2);
	build(mid+1,r,root*2+1);
	summ[root]=summ[root<<1]+summ[(root<<1)+1];  //两个子树的和 
	mx[root]=max(mx[root<<1],mx[(root<<1)+1]);  //两个子树的最大值 
}
void upda(int root,int l,int r,int x,int y){
	//看这里为什么需要mx数组!!! 
	if(mx[root]==1||mx[root]==0) return; //不需要改变值了
	if(l==r){
		summ[root]=mx[root]=int(sqrt(summ[root]));
		return;
	} 
	int mid=(l+r)/2;
	if(x<=mid) upda(root*2,l,mid,x,y);
	if(y>mid) upda(root*2+1,mid+1,r,x,y);
	summ[root]=summ[root*2]+summ[root*2+1];
	mx[root]=max(mx[root*2],mx[root*2+1]);
}
LL getans(int root,int l,int r,int x,int y){
	if(x<=l&&r<=y) return summ[root];
	int mid=(l+r)/2;
	LL ans=0;
	if(x<=mid) ans+=getans(root*2,l,mid,x,y);
	if(y>mid) ans+=getans(root*2+1,mid+1,r,x,y);
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&num[i]);
	}
	build(1,n,1);  //别忘列写这个TAT 
	LL xx,ll,rr;
	scanf("%d",&m);
	while(m--){
		scanf("%lld %lld %lld",&xx,&ll,&rr);
		if(xx==1){
			printf("%lld\n",getans(1,1,n,ll,rr));
		}
		else{
			upda(1,1,n,ll,rr);
		}
	}
return 0;
}

  

1551:维护序列

是区间修改,区间求和
//但是修改有两种方式:1、全部乘一个值 2、全部加一个值
//https://www.cnblogs.com/lher/p/6556238.html
//https://blog.csdn.net/weixin_43323172/article/details/99689300

经典线段树题目,同时有两个标记,一个加法标记,一个乘法标记,每个标记维护的意义为:下面的子树中,要先把每一项都乘以乘法标记,再加上加法标记。

设序列A = {a1,a2,a3,…,an},如果每一项先乘以p1,则序列变为{p1*a1,p1*a2,p1*a3,...,p1*an},再加上p2,则序列变为{p1*a1+p2,p1*a2+p2,p1*a3+p2,...,p1*an+p2},
再乘以p3,则序列变为{p1*p3*a1+p2*p3,p1*p3*a2+p2*p3,p1*p3*a3+p2*p3,...,p1*p3*an+p2*p3}。
由此可见,在添加标记或者下放标记合并时,
若新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
若新加加法标记,则与前面的乘法标记无关,直接加在加法标记上,区间和加上区间长度*加法标记。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//也是区间修改,区间求和
//但是修改有两种方式:1、全部乘一个值  2、全部加一个值
//https://www.cnblogs.com/lher/p/6556238.html
//https://blog.csdn.net/weixin_43323172/article/details/99689300
LL n,p,m; 
LL summ[maxn*4];
//要加上Lazy操作,不然会超时 
LL lazy_add[maxn*4],lazy_mul[maxn*4];
LL num[maxn];
void add(int v,int l,int r,int root){
	//区间整体加 
	lazy_add[root]=(lazy_add[root]+v%p)%p;
	summ[root]=(summ[root]+(LL)v*(r-l+1)%p)%p;
}
/*
经典线段树题目,同时有两个标记,一个加法标记,一个乘法标记,每个标记维护的意义为:下面的子树中,要先把每一项都乘以乘法标记,再加上加法标记。

设序列A = {a1,a2,a3,…,an},如果每一项先乘以p1,则序列变为{p1*a1,p1*a2,p1*a3,...,p1*an},再加上p2,则序列变为{p1*a1+p2,p1*a2+p2,p1*a3+p2,...,p1*an+p2},
再乘以p3,则序列变为{p1*p3*a1+p2*p3,p1*p3*a2+p2*p3,p1*p3*a3+p2*p3,...,p1*p3*an+p2*p3}。
由此可见,在添加标记或者下放标记合并时,
若新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
若新加加法标记,则与前面的乘法标记无关,直接加在加法标记上,区间和加上区间长度*加法标记。
*/
void mul(int v,int l,int r,int root){
	lazy_mul[root]=(lazy_mul[root]*v)%p;
	lazy_add[root]=(lazy_add[root]*v)%p;   //新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
	summ[root]=(summ[root]*v)%p;
} 
void push_down(int mm,int l,int r,int root){
	if(lazy_mul[root]!=1){
	//	int mid=(l+r)/2;
		mul(lazy_mul[root],l,mm,root*2);
		mul(lazy_mul[root],mm+1,r,root*2+1);
		lazy_mul[root]=1;
	}
	if(lazy_add[root]!=0){
	//	int mid=(l+r)/2;
		add(lazy_add[root],l,mm,root*2);
		add(lazy_add[root],mm+1,r,root*2+1);
		lazy_add[root]=0;
	}
}
void build(LL l,LL r,LL root){
	summ[root]=0;
	lazy_add[root]=0;
	lazy_mul[root]=1; 
	if(l==r) {
		summ[root]=num[l];
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,root*2);
	build(mid+1,r,root*2+1);
	summ[root]=(summ[root*2]+summ[root*2+1])%p;
}
void upda(int root,int l,int r,int x,int y,int flag,int c){
	if(x<=l&&r<=y) {
		if(flag==1) return mul(c,l,r,root);
		if(flag==2) return add(c,l,r,root) ;
		//return;
	}
	int mid=(l+r)/2;
	push_down(mid,l,r,root);  //int mm,int l,int r,int root
	if(x<=mid) upda(root*2,l,mid,x,y,flag,c);
	if(y>mid) upda(root*2+1,mid+1,r,x,y,flag,c);
	summ[root]=(summ[root*2]+summ[root*2+1])%p;
}
LL getans(int root,int l,int r,int x,int y){
	if(x<=l&&r<=y) return summ[root];
	int mid=(l+r)/2;
	push_down(mid,l,r,root);
	
	LL ans=0;
	if(x<=mid) ans=(ans+getans(root*2,l,mid,x,y))%p;
	if(y>mid) ans=(ans+getans(root*2+1,mid+1,r,x,y))%p;
	return ans%p;
}
int main(){
	scanf("%lld %lld",&n,&p);
	for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
	build(1,n,1);
	int op,g,c,t;
	scanf("%d",&m);
	while(m--){
		scanf("%d",&op);
		if(op==1||op==2){
			scanf("%d %d %d",&t,&g,&c);
			 upda(1,1,n,t,g,op,c);
		}
		else if(op==3){
			scanf("%d %d",&t,&g);
			printf("%lld\n",getans(1,1,n,t,g));
		}
	}
return 0;
}

  

 

 
 posted on 2020-04-25 13:19  shirlybabyyy  阅读(324)  评论(0编辑  收藏  举报