区间修改的RMQ问题
时间限制: 1 Sec 内存限制: 128 MB
题目描述
给出N(1 ≤ N ≤ 50,000)个数的序列A,下标从1到N,每个元素值均不超过1000。有两种操作:
(1) Q i j:询问区间[i, j]之间的最大值与最小值的差值
(2) C i j k:将区间[i, j]中的每一个元素增加k,k是一个整数,k的绝对值不超过1000。
一共有M (1 ≤ M ≤ 200,000) 次操作,对每个Q操作,输出一行,回答提问。
输入
第1行:2个整数N, M
第2行:N个元素
接下来M行,每行一个操作
输出
样例输入
5 4
1 2 3 4 5
Q 2 4
C 1 1 1
C 1 3 2
Q 1 5
样例输出
2
1
才略懂了一点zkw线段树,本来觉得自己并没有灵活掌握全部,但是理解是靠熟练一点点堆起来的,
所以才慢慢写一篇题解,想看看自己是不是真的懂了一点点,
以下全部都是我自己对于zkw线段树的理解,仅作参考,如果有问题,还望指教。
使用差分思想,令Tr[i]中维护它与其父亲节点的差值(待会儿会写另一篇来详细介绍我对其的理解)
为什么是差值呢?
因为这样能够方便修改,当一个节点的整个子树都被包含在内的时候,只需要修改此节点及以上,就可以达到修改整个子树的目的,以节约时间。
即维持下方节点差值不变,将上方差值增大,这样可以保证结果不变。
因为,对于每个子节点的真实值,都需要从子节点一路累加到根节点。
写在前面:
我的zkw树下标和网上的不太一样,我并没有用开区间,全部代码都为闭区间。
以下代码仅以求最小值为例。
请仔细阅读build():
void build(){
for(X=1; X<=N; X<<=1);
--X;
for(int i=X+1; i<=X+N; ++i)scanf("%d",&Tr[i]);
for(int i=X; i>=1; --i)
Tr[i]=min(Tr[i<<1],Tr[i<<1|1]),Tr[i<<1]-=Tr[i],Tr[i<<1|1]-=Tr[i];
}
单个子节点求真实值代码如下:
int get_value(int pos){
int val=0;
for(pos+=X; pos; pos>>=1)
val+=Tr[pos];
return val;
}
对于单个子节点的修改如下:
void add(int pos,int val){
int A;
Tr[pos]+=val;
for(; pos>1; pos>>=1)
A=min(Tr[pos],Tr[pos^1]),Tr[pos]-=A,Tr[pos^1]-=A,Tr[pos>>1]+=A;
}
对于区间的修改如下:
void add(int s,int t,int val){
s+=X,t+=X;
int A;
if(s==t){//注意单点是需要特判的,不然会死循环
Tr[s]+=val;
for(; s>1; s>>=1)
A=min(Tr[s],Tr[s^1]),Tr[s]-=A,Tr[s^1]-=A,Tr[s>>1]+=A;
return ;
}
Tr[s]+=val;Tr[t]+=val;
for(; s^t^1; s>>=1,t>>=1){
if(~s&1)Tr[s^1]+=val;//判断是不是左子,若是就将右子纳入比较
if(t&1)Tr[t^1]+=val;//判断是不是右子,若是就将左子纳入比较
A=min(Tr[s],Tr[s^1]),Tr[s]-=A,Tr[s^1]-=A,Tr[s>>1]+=A;//修改该点与其父亲节点的关系,稍后会在另一篇文章中详细讲述解释(是我自己的理解,仅做参考)
A=min(Tr[t],Tr[t^1]),Tr[t]-=A,Tr[t^1]-=A,Tr[t>>1]+=A;
}
while(s!=1){//因为是把s为子节点去更新s>>1,所以s只能到2就停下,此时(s>>1)=1
A=min(Tr[s],Tr[s^1]),Tr[s]-=A,Tr[s^1]-=A,Tr[s>>1]+=A;
s>>=1;
}
}
本题开两个数组,一个维护最小值,一个维护最大值。
完整代码如下:
#include
#include
#include
using namespace std;
const int Max=50000;
int N,M,X;
int Trx[Max*4+5],Trn[Max*4+5];
void build(){
for(X=1; X<=N; X<<=1);
for(int i=1; i<=(X<<1); ++i)Trn[i]=0x3f3f3f,Trx[i]=-0x3f3f3f;
--X;
for(int i=X+1; i<=X+N; ++i)scanf("%d",&Trx[i]),Trn[i]=Trx[i];
for(int i=X; i>=1; --i){
Trx[i]=max(Trx[i<<1],Trx[i<<1|1]),Trx[i<<1]-=Trx[i],Trx[i<<1|1]-=Trx[i];
Trn[i]=min(Trn[i<<1],Trn[i<<1|1]),Trn[i<<1]-=Trn[i],Trn[i<<1|1]-=Trn[i];
}
}
void add(int s,int t,int val){
s+=X,t+=X;
int A;
if(s==t){
Trx[s]+=val,Trn[s]+=val;
for(; s>1; s>>=1){
A=min(Trn[s],Trn[s^1]),Trn[s]-=A,Trn[s^1]-=A,Trn[s>>1]+=A;
A=max(Trx[s],Trx[s^1]),Trx[s]-=A,Trx[s^1]-=A,Trx[s>>1]+=A;
}
return ;
}
Trx[s]+=val,Trn[s]+=val,Trx[t]+=val,Trn[t]+=val;
for(; s^t^1; s>>=1,t>>=1){
if(~s&1)Trx[s^1]+=val,Trn[s^1]+=val;
if(t&1)Trx[t^1]+=val,Trn[t^1]+=val;
A=min(Trn[s],Trn[s^1]),Trn[s]-=A,Trn[s^1]-=A,Trn[s>>1]+=A;
A=min(Trn[t],Trn[t^1]),Trn[t]-=A,Trn[t^1]-=A,Trn[t>>1]+=A;
A=max(Trx[s],Trx[s^1]),Trx[s]-=A,Trx[s^1]-=A,Trx[s>>1]+=A;
A=max(Trx[t],Trx[t^1]),Trx[t]-=A,Trx[t^1]-=A,Trx[t>>1]+=A;
}
while(s!=1){
A=min(Trn[s],Trn[s^1]),Trn[s]-=A,Trn[s^1]-=A,Trn[s>>1]+=A;
A=max(Trx[s],Trx[s^1]),Trx[s]-=A,Trx[s^1]-=A,Trx[s>>1]+=A;
s>>=1;
}
}
int Query(int s,int t){
s+=X,t+=X;
if(s==t){
int ansx=Trx[s],ansn=Trn[s];
while(s){
s>>=1;
ansx+=Trx[s],ansn+=Trn[s];
}
return ansx-ansn;
}
int ansxl,ansxr,ansnl,ansnr;
ansxl=ansxr=ansnl=ansnr=0;
for(; s^t^1; s>>=1,t>>=1){
ansxl+=Trx[s],ansxr+=Trx[t];
ansnl+=Trn[s],ansnr+=Trn[t];
if(~s&1){
ansnl=min(ansnl,Trn[s^1]);
ansxl=max(ansxl,Trx[s^1]);
}
if(t&1){
ansnr=min(ansnr,Trn[t^1]);
ansxr=max(ansxr,Trx[t^1]);
}
}
ansxl+=Trx[s],ansxr+=Trx[t],ansnl+=Trn[s],ansnr+=Trn[t];
int ansx=max(ansxl,ansxr),ansn=min(ansnl,ansnr);
while(s){
s>>=1;
ansx+=Trx[s],ansn+=Trn[s];
}
return ansx-ansn;
}
int main(){
scanf("%d%d",&N,&M);
build();
char ord[3];
int l,r,val;
while(M--){
scanf("%s%d%d",ord,&l,&r);
if(ord[0]=='Q')
printf("%d\n",Query(l,r));
else {
scanf("%d",&val);
add(l,r,val);
}
}
return 0;
}