首先,我们可以想到用线段树来做这道题。不会告诉你其实我不会树状数组
数据太大就会超时,怎么办?这样就要引出我们的好帮手懒标记了!
一个支持区间修改的线段树, 就要用到lazy标记. 用到哪一个结点, 有效数据就更新到哪一个结点, 避免浪费更新那些不必要的结点的时间. 从根部向下找一个结点, 一路下来根据lazy的设定进行相关的更新操作, 就能快速完成任务.
下面就示范一组样例:
8 2
3 3 3 3 3 3 3 3
1 2 7 1
2 3
相信大家对线段树已经有了一定的了解(没了解过的看这里洛谷日报4)那我们就开始构造线段树,如图:
这就是基本的线段树了,然后开始进行第一步,从2到7,一次给区间加上1:
来解释这一幅图,首先看我们存线段树的下标1,L(左节点)=1,R(右节点)=8,没有被包含且懒标记=0,所以取中点mid=(1+8)/2=4,查找左右子树。
来到左子树2,L=L(father’s),R=mid(father’s),没有被完全包含且懒标记=0,所以继续查找左右子树。
来到2的左子树4,没有被包含且懒标记=0,所以查找左右子树,因为左子树的右节点大于2,所以不进入。来到右子树9,被包含,所以dis[9]+=1,懒标记+=1,返回。
返回到2,查找右子树5,被包含,所以dis[5]+=(4-3+1)* 1,懒标记+=1,返回。
回到1,查找右节点3……以此类推,dis[6]+=(6-5+1)* 1,懒标记+=1;dis[14]+=1,懒标记+=1;
这样,区间修改就完成了。
来到第二步,查找3:
从1开始找到2,在找到5,发现有懒标记,所以把懒标记下传到10和11, dis[10]+=1;懒标记[10]+=1;dis[11]+=1,懒标记[11]+=1,懒标记[5]=0; 到10,L=R=3,return dis[10];查询就完成了。
可能有人会说懒标记看起来没有什么用,但数据一旦很大,递归传递就显得尤为方便,可以为我们节省很多时间。
接下来是对代码的详细讲解:
初始化:
struct Tree{
long long l,r;//l:左节点 r:右节点
long long dat,laze_tag;//dat:当前节点的值 laze_tag:懒标记,记录改变的值,递归传值
}t[2000001];//四倍n
然后是我们的懒标记传递:
inline void f(long long p,long long k){
t[p].laze_tag+=k;//懒标记传递
t[p].dat+=k*(t[p].r-t[p].l+1);//当前值加上所有节点总数*值
}
inline void pushdown(long long p){
f(p*2,t[p].laze_tag);
f(p*2+1,t[p].laze_tag);
//将懒标记的值传给下面的左右儿子节点
t[p].laze_tag=0;//复原懒标记
}
(下面会有再查找和修改时的懒标记处理)
开始先要构造我们的线段树
void js(int p,int l,int r){//建树
t[p].l=l;t[p].r=r;//记录左右节点
if(l==r){//到达底部返回值
t[p].dat=a[l];return;
}
long long mid=(l+r)/2;//中点
js(p*2,l,mid);
js(p*2+1,mid+1,r);
//递归初始化
t[p].dat=t[p*2].dat+t[p*2+1].dat;
//加上左右儿子节点
}
第二步,就要写我们的区间加减了
void pushs(long long p,long long l,long long r,long long v){//区间加减
if(t[p].l>=l&&t[p].r<=r){//如果区间被包含就修改并打上懒标记
t[p].dat+=v*(t[p].r-t[p].l+1);//加上所有值
t[p].laze_tag+=v;//懒标记修改
return;
}
pushdown(p);//查询懒标记,因为下面要递归
long long mid=(t[p].r+t[p].l)/2;//取中点
if(l<=mid)pushs(p*2,l,r,v);//修改左边
if(r>mid) pushs(p*2+1,l,r,v);//修改右边
t[p].dat=t[p*2].dat+t[p*2+1].dat;//回溯时加上左右儿子节点的值
}
最后是最容易的单点查询
long long outt(long long p,long long l){//单点查询
if(t[p].l==l&&t[p].r==l)return t[p].dat;//找到目标点就返回
pushdown(p);//先回复懒标记的值再传递,因为下面可能递归(要判断是否到了底部,就是这里出了问题QwQ)
long long mid=(t[p].l+t[p].r)/2;//记录中点
if(l<=mid)return outt(p*2,l);//找左边
if(l>mid)return outt(p*2+1,l);//找右边
}
看完了,那就贴上完整的代码啦!
#include<bits/stdc++.h>
using namespace std;
long long n,m;//n:长度 m: 询问
long long a[500001];
struct Tree{
long long l,r;//l:左节点 r:右节点
long long dat,laze_tag;
}t[2000001];
inline long long read(){
long long f=1,outt=0;char a=getchar();
while(a>'9'||a<'0'){if(a=='-')f=-1;a=getchar();}
while(a<='9'&&a>='0'){outt*=10;outt+=a-'0';a=getchar();}
return f*outt;
}//读入优化
inline void f(long long p,long long k){
t[p].laze_tag+=k;
t[p].dat+=k*(t[p].r-t[p].l+1);
}
inline void pushdown(long long p){
f(p*2,t[p].laze_tag);
f(p*2+1,t[p].laze_tag);
t[p].laze_tag=0;
}
void js(int p,int l,int r){
t[p].l=l;t[p].r=r;
if(l==r){
t[p].dat=a[l];return;
}
long long mid=(l+r)/2;//中点
js(p*2,l,mid);
js(p*2+1,mid+1,r);
t[p].dat=t[p*2].dat+t[p*2+1].dat;
}
long long outt(long long p,long long l){
if(t[p].l==l&&t[p].r==l)return t[p].dat;
pushdown(p);
long long mid=(t[p].l+t[p].r)/2;
if(l<=mid)return outt(p*2,l);
if(l>mid)return outt(p*2+1,l);
}
void pushs(long long p,long long l,long long r,long long v){
if(t[p].l>=l&&t[p].r<=r){
t[p].dat+=v*(t[p].r-t[p].l+1);
t[p].laze_tag+=v;
return;
}
pushdown(p);
long long mid=(t[p].r+t[p].l)/2;
if(l<=mid)pushs(p*2,l,r,v);
if(r>mid) pushs(p*2+1,l,r,v);
t[p].dat=t[p*2].dat+t[p*2+1].dat;
}
void change(long long p,int x,int v){//单点修改,不必在意,是区间修改的子问题,连标记都不用(而且本题不需要)
if(t[p].l==t[p].r){
t[p].dat+=v;return;
}
int mid=(t[p].r+t[p].l)/2;
if(x<=mid)change(p*2,x,v);
else change(p*2+1,x,v);
t[p].dat=t[p*2].dat+t[p*2+1].dat;
}
int main(){
n=read();m=read();//读入
for(int i=1;i<=n;i++)
a[i]=read();
js(1,1,n);//建树
for(int i=1;i<=m;i++){
long long pd=read();
if(pd==2){
long long ll=read();
printf("%lld\n",outt(1,ll));//查询ll的值
}
else
if(pd==1){
long long ll=read(),rr=read(),x=read();
pushs(1,ll,rr,x);//修改从ll到rr的值加上x
}
else
if(pd==3){
int k=read(),y=read();
change(1,k,y);
}
}
return 0;//华丽丽的结束,可以A掉树状数组2了!!!
}