線段樹 \(2\)
接上一講
https://www.cnblogs.com/yingxilin/p/18350988
(沒看的同學們可以先看這篇)
上一講裡我們已經介紹了單點修改,區間查詢的線段樹了。
在這一講裡,我們開始學習支援區間修改,區間查詢的線段樹。
考慮之前的做法,之前的查詢區間會被分為 \(O(logn)\),從而求解,但因為所有子樹節點要一起更新, 複雜度淪為 \(O(n)\) ,無法接受。
tag-懶惰標記
這時,我們可以給要修改的點加一個懶惰標記 \(tag\) ,標記為“該點已經被修改,其子節點尚未被更新”,再將其下傳。之後在遞迴進入其父節點時再判斷其是否帶有 \(tag\) 標記,若有,再將其更新。如此一來,就不用修改一個點時更新所有子樹節點,只需要把要用到的節點更新,大大減小了演算法複雜度。具體操作看程式碼,有註釋。
區間更改,區間查詢
例題:
https://www.luogu.com.cn/problem/P3372
大意:區間加,區間求和
程式碼:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define FOR(i,_l,_r) for(int i=_l;i<=_r;i++)
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)//這裡一定要記得打上括號,不然會調得很慘
#define len(x) (t[x].r-t[x].l+1)
const int N=1e5+5;
struct Node{
int l,r;//左右兒子
int sum,tag;//區間和,懶惰標記
}t[N<<2];
int a[N];
int n,m;
void up(int p){//向上更新
t[p].sum=t[ls].sum+t[rs].sum;
}
void build(int p,int l,int r){//建樹
t[p].l=l;t[p].r=r;
if(l==r){
t[p].sum=a[l];
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
up(p);
}
void down(int p){//懶惰標記下放
if(!t[p].tag) return;//無tag,return;
int k=t[p].tag;
t[ls].sum+=k*len(ls);//更新左區間
t[rs].sum+=k*len(rs);//更新右區間
t[ls].tag+=k;//傳給左兒子
t[rs].tag+=k;//傳給右兒子
t[p].tag=0;//記得把當前標記清零
}
void change(int p,int L,int R,int v){//修改
int l=t[p].l;
int r=t[p].r;
if(L<=l&&r<=R){
t[p].sum+=v*len(p);//更新當前節點的區間和
t[p].tag+=v;//更新tag
return;
}
down(p);//下放
if(L<=mid) change(ls,L,R,v);
if(R>mid) change(rs,L,R,v);
up(p);
}
int query(int p,int L,int R){//查詢
int l=t[p].l;
int r=t[p].r;
if(L<=l&&r<=R) return t[p].sum;
down(p);//下放
int ans=0;
if(L<=mid) ans+=query(ls,L,R);
if(R>mid) ans+=query(rs,L,R);
return ans;
}
signed main(){
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
// ios::sync_with_stdio(false);
// cin.tie(NULL);
cin>>n>>m;
FOR(i,1,n) cin>>a[i];
build(1,1,n);//建樹
while(m--){
// cout<<1;
int opt,x,y,k;
cin>>opt>>x>>y;
if(opt==1){
cin>>k;
change(1,x,y,k);
}
else cout<<query(1,x,y)<<endl;
}
return 0;
}
留個作業
https://www.acwing.com/problem/content/description/263/