1 概述
在可持久化線段樹中,我們常常要使用區間修改操作。這時候,如果再用下傳標記再向上更新的方式(PushDown&PushUp)來實現就會變得十分麻煩(因為要可持久化嘛)。
那么,有沒有一種實現線段樹區間修改的方式可以不用下傳標記或向上更新呢?有,那就是標記永久化。
2 原理
標記永久化的原理簡單來說就是修改時一路更改被影響到的點,詢問時則一路累加路上的標記,從而省去下傳標記的操作。
3 代碼實現
3.0 說明
這里以區間修改區間求和的線段樹為例。
線段樹中編號為p的結點的值和標記分別為val[p]和mark[p]。
3.1 建樹
標記永久化線段樹的建樹和標記不永久化線段樹的建樹沒有什么區別,這里就不在贅述,直接上代碼吧。
void Build(int p,int l,int r) { if(l==r) {scanf("%lld",&val[p]);return;} int mid=(l+r)>>1; Build(p<<1,l,mid);//遞歸建左子樹 Build(p<<1|1,mid+1,r);//遞歸建右子樹 val[p]=val[p<<1]+val[p<<1|1];//這里是要向上更新一下的 }
3.2 區間修改
0.設要將區間[x,y]中的數都加上v。
1.一路走下去同時更新路上受此次修改影響的節點的值,即val[p]+=(y-x+1)*v。
2.當目前結點所代表的區間與待修改區間完全重合時,更新標記,返回,即mark[p]+=v;
void add(int p,int l,int r,int x,int y,long long v) { val[p]+=(y-x+1)*v;//更新該結點的權值 if(l==x&&r==y) {mark[p]+=v;return;}//更新標記 int mid=(l+r)>>1; if(y<=mid) add(p<<1,l,mid,x,y,v); else if(x>mid) add(p<<1|1,mid+1,r,x,y,v); else add(p<<1,l,mid,x,mid,v),add(p<<1|1,mid+1,r,mid+1,y,v); }
有人可能會問:標記更新后直接返回的話下面的結點不就沒更新了嗎?
慢慢來嘛,往下看就明白啦。
3.3 區間詢問
0.設要要區間[x,y]中的數的總和。
1.一路走下去同時累加路上的標記,因為在修改操作中標記並沒有下傳,所以要這樣子,即ad+=mark[p]。
2.當目前結點所代表的區間與待修改區間完全重合時,返回當前結點的值與累加下來的標記乘上詢問區間長度的和,即return val[p]+(y-x+1)*ad。
int ask(int p,int l,int r,int x,int y,int ad)//ad為一路上累加的標記 { if(l==x&&r==y) return val[p]+(y-x+1)*ad; int mid=(l+r)>>1; if(y<=mid) return ask(p<<1,l,mid,x,y,ad+mark[p]); if(x>mid) return ask(p<<1|1,mid+1,r,x,y,ad+mark[p]); return ask(p<<1,l,mid,x,mid,ad+mark[p])+ask(p<<1|1,mid+1,r,mid+1,y,ad+mark[p]); }
4 練習題
代碼:
#include<iostream> #include<cstdio> using namespace std; long long val[400005],mark[400005]; void Build(int p,int l,int r) { if(l==r) {scanf("%lld",&val[p]);return;} int mid=(l+r)>>1; Build(p<<1,l,mid); Build(p<<1|1,mid+1,r); val[p]=val[p<<1]+val[p<<1|1]; } void add(int p,int l,int r,int x,int y,long long v) { val[p]+=(y-x+1)*v; if(l==x&&r==y) {mark[p]+=v;return;} int mid=(l+r)>>1; if(y<=mid) add(p<<1,l,mid,x,y,v); else if(x>mid) add(p<<1|1,mid+1,r,x,y,v); else add(p<<1,l,mid,x,mid,v),add(p<<1|1,mid+1,r,mid+1,y,v); } long long ask(int p,int l,int r,int x,int y,long long ad) { if(l==x&&r==y) return val[p]+(y-x+1)*ad; int mid=(l+r)>>1; if(y<=mid) return ask(p<<1,l,mid,x,y,ad+mark[p]); if(x>mid) return ask(p<<1|1,mid+1,r,x,y,ad+mark[p]); return ask(p<<1,l,mid,x,mid,ad+mark[p])+ask(p<<1|1,mid+1,r,mid+1,y,ad+mark[p]); } int main() { int n=0,m=0; scanf("%d%d",&n,&m); Build(1,1,n); int opt=0,x=0,y=0; long long k=0; for(int i=1;i<=m;i++) { scanf("%d%d%d",&opt,&x,&y); if(opt==1) { scanf("%lld",&k); add(1,1,n,x,y,k); } else printf("%lld\n",ask(1,1,n,x,y,0)); } return 0; }
5 參考資料
https://www.cnblogs.com/Hallmeow/p/8004676.html
