②n=9html
二.線段樹的構造與實現ios
1.建樹與維護c++
根據線段樹的性質,咱們能夠獲得一個節點的左兒子和右兒子的表示方法(上面有提QwQ)數組
inline ll ls(ll x)//左兒子 { return x<<1;//至關於x*x } inline ll rs(ll x)//右兒子 { return (x<<1)|1;//至關於x*2+1 }
因而就有了維護的代碼:函數
inline void push_up_sum(ll p)//向上維護區間和 { ans[p]=ans[ls(p)]+ans[rs(p)]; } inline void push_up_min(ll p)//向上維護區間最小值 { ans[p]=min(ans[ls(p)],ans[rs(p)]); } inline void push_up_max(ll p)//向上維護區間最大值 { ans[p]=max(ans[ls(p)],ans[rs(p)]); }
須要注意的是,這裏是從下往上維護父子節點的邏輯關係,由於當你一個子節點改變了以後,它全部的父親節點都須要改變。因此開始遞歸以後必然是先去整合子節點的信息,再向它們的祖先反饋整合以後的信息。優化
因此對於建樹,因爲二叉樹的結構特色,咱們能夠選擇遞歸建樹,而且在遞歸的過程當中要注意維護父子關係ui
void build(ll p,ll l,ll r)//p 根節點,l 區間左端點,r 區間右端點 { tag[p]=0;//建樹時順便把懶惰標記清零(這個後面會提到) if(l==r)//若是左右端點相等,就說明已經到達了葉子結點 { ans[p]=a[l]; return ; } ll mid=(l+r)>>1;//左右子樹分別遞歸建樹 build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p);//記得維護父子關係 }
咱們已經有了一棵線段樹,可是如今並不能對它進行什麼操做,它目前還並無什麼卵用spa
2.區間修改code
假如咱們要修改一個區間[l,r]內的值,咱們發現因爲二叉樹的結構特色,l和r極可能不在同一個節點上,這時候怎麼辦?htm
區間拆分了解一下
區間拆分是線段樹的核心操做,咱們能夠將一個區間[a, b]拆分紅若干個節點,使得這些節點表明的區間加起來是[a, b],而且相互之間不重疊.
全部咱們找到的這些節點就是」終止節點」.
區間拆分的步驟
從根節點[1, n]開始,考慮當前節點是[L, R].
若是[L, R]在[a, b]以內,那麼它就是一個終止節點.
不然,分別考慮[L, Mid],[Mid + 1, R]與[a, b]是否有交,遞歸兩邊繼續找終止節點
舉個栗子:
好比咱們要從[1,10]中拆分出[2,8]這個區間
仍是挺直觀的吧QwQ
這實際上是一種分塊的思想
分塊的思想是經過將整個序列分爲有窮個小塊,對於要查詢的一段區間,老是能夠整合成k個所分塊與m個單個元素的信息的並
因此咱們應該充分利用區間拆分的性質,思考在終止節點要存什麼信息,如何快速維護這些信息,不要每次一變就到最底層
那麼對於區間操做,咱們引入一個東西——懶標記(lazy tag)。那這個東西「lazy」在什麼地方呢?
咱們發現本來的區間修改須要經過改變最下的葉子結點,而後不斷向上遞歸來修改祖先節點直至到達根節點,時間複雜度最多能夠到O(n logn)的級別。
可是當咱們用了懶標記後,時間複雜度就下降到了O(log n)的級別甚至更低
懶標記怎麼用?
若是整個區間都被操做,就把懶標記記錄在公共祖先節點上,若是隻修改了一部分,就記錄在這部分的公共祖先上,若是隻修改了本身的話,就只改變本身
而後,若是咱們採用這種方式優化的話,咱們須要在每一次區間查詢修改時pushdown一次,以避免重複衝突
那麼怎麼傳導push down呢?
開始回溯是執行push up,由於是向上傳導信息;若是咱們想要他向下更新,就調整順序,在向下遞歸的時候push down
代碼:
inline void f(ll p,ll l,ll r,ll k) { tag[p]=tag[p]+k; ans[p]=ans[p]+k*(r-l+1); //因爲是這個區間統一改變,因此ans數組要加元素個數 } //f函數的惟一目的,就是記錄當前節點所表明的區間 inline void push_down(ll p,ll l,ll r) { ll mid=(l+r)>>1; f(ls(p),l,mid,tag[p]); f(rs(p),mid+1,r,tag[p]); tag[p]=0; //不斷向下遞歸更新子節點 } inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k) { //nl,nr爲要修改的區間 //l,r,p爲當前節點所存儲的區間以及節點的編號 //k爲要增長的值 if(nl<=l&&r<=nr) { ans[p]+=k*(r-l+1); tag[p]+=k; return; } push_down(p,l,r); //回溯以前(也能夠說是下一次遞歸以前,由於沒有遞歸就沒有回溯) //因爲是在回溯以前不斷向下傳遞,因此天然每一個節點均可以更新到 ll mid=(l+r)>>1; if(nl<=mid) update(nl,nr,l,mid,ls(p),k); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k); push_up(p); //回溯以後 }
對於複雜度而言,因爲徹底二叉樹的深度不超過logn,那麼單點修改顯然是O(logn)的,而區間修改的話,因爲咱們是分了log n個區間,每次查詢是O(1)的複雜度,因此複雜度是O(log n)的
3.區間查詢
這裏仍是用到了分塊的思想,對於要查詢的區間作區間分解,而後遞歸求答案就行了
ll find(ll qx,ll qy,ll l,ll r,ll p) { //qx 區間左端點 qy區間右端點 //l當前左端點 r當前右端點 //p當前區間的編號 ll res=0; if(qx<=l&&r<=qy) return ans[p]; ll mid=(l+r)>>1; push_down(p,l,r); if(qx<=mid) res+=find(qx,qy,l,mid,ls(p)); if(qy>mid) res+=find(qx,qy,mid+1,r,rs(p)); return res; }
三.標程
#include<cstdio> #include<iostream> #include<cstdlib> #include<iomanip> #include<cmath> #include<cstring> #include<string> #include<algorithm> #include<time.h> #include<queue> using namespace std; typedef long long ll; typedef long double ld; typedef pair<int,int> pr; const double pi=acos(-1); #define rep(i,a,n) for(int i=a;i<=n;i++) #define per(i,n,a) for(int i=n;i>=a;i--) #define Rep(i,u) for(int i=head[u];i;i=Next[i]) #define clr(a) memset(a,0,sizeof a) #define pb push_back #define mp make_pair #define fi first #define sc second ld eps=1e-9; ll pp=1000000007; ll mo(ll a,ll pp){if(a>=0 && a<pp)return a;a%=pp;if(a<0)a+=pp;return a;} ll powmod(ll a,ll b,ll pp){ll ans=1;for(;b;b>>=1,a=mo(a*a,pp))if(b&1)ans=mo(ans*a,pp);return ans;} ll read(){ ll ans=0; char last=' ',ch=getchar(); while(ch<'0' || ch>'9')last=ch,ch=getchar(); while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar(); if(last=='-')ans=-ans; return ans; } //head const int MAXN=1000001; ll a[MAXN],ans[MAXN<<2],tag[MAXN<<2]; ll m,n; inline ll ls(ll x) { return x<<1; } inline ll rs(ll x) { return (x<<1)+1; } inline void push_up(ll p) { ans[p]=ans[ls(p)]+ans[rs(p)]; } inline void f(ll p,ll l,ll r,ll k) { tag[p]=tag[p]+k; ans[p]=ans[p]+k*(r-l+1); } inline void push_down(ll p,ll l,ll r) { ll mid=(l+r)>>1; f(ls(p),l,mid,tag[p]); f(rs(p),mid+1,r,tag[p]); tag[p]=0; } void build(ll p,ll l,ll r) { tag[p]=0; if(l==r) { ans[p]=a[l]; return ; } ll mid=(l+r)>>1; build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p); } inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k) { if(nl<=l&&r<=nr) { ans[p]+=k*(r-l+1); tag[p]+=k; return; } push_down(p,l,r); ll mid=(l+r)>>1; if(nl<=mid) update(nl,nr,l,mid,ls(p),k); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k); push_up(p); } ll find(ll qx,ll qy,ll l,ll r,ll p) { ll res=0; if(qx<=l&&r<=qy) return ans[p]; ll mid=(l+r)>>1; push_down(p,l,r); if(qx<=mid) res+=find(qx,qy,l,mid,ls(p)); if(qy>mid) res+=find(qx,qy,mid+1,r,rs(p)); return res; } int main() { n=read(),m=read(); rep(i,1,n) a[i]=read(); build(1,1,n); while(m--) { ll a1=read(); if(a1==1) { ll b=read(),c=read(),d=read(); update(b,c,1,n,1,d); } if(a1==2) { ll b=read(),c=read(); printf("%lld\n",find(b,c,1,n,1)); } } return 0; }
特別鳴謝:_皎月半灑花 大佬題解
water_lift:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll N=100001; ll sum[N<<2],lazy[N<<2]; ll a[N]; void build(ll cnt,ll l,ll r) { if(l==r) { sum[cnt]=a[l]; return; } else { ll mid=(l+r)>>1; build(cnt<<1,l,mid); build((cnt<<1)|1,mid+1,r); sum[cnt]=sum[cnt<<1]+sum[(cnt<<1)+1]; } } inline bool cover(ll nl,ll nr,ll l,ll r) { return l<=nl&&r>=nr; } inline bool intersection(ll nl,ll nr,ll l,ll r) { return l<=nr&&r>=nl; } void pushdown(ll cnt,ll l,ll r) { ll mid=(l+r)>>1; lazy[cnt<<1]+=lazy[cnt]; lazy[(cnt<<1)+1]+=lazy[cnt]; sum[cnt<<1]+=lazy[cnt]*(mid-l+1); sum[(cnt<<1)+1]+=lazy[cnt]*(r-mid); lazy[cnt]=0; } void add(ll cnt,ll nl,ll nr,ll l,ll r,ll a) { if(cover(nl,nr,l,r)) { sum[cnt]+=(nr-nl+1)*a; lazy[cnt]+=a; return ; } pushdown(cnt,nl,nr); ll mid=(nl+nr)>>1; if(intersection(nl,mid,l,r)) add(cnt<<1,nl,mid,l,r,a); if(intersection(mid+1,nr,l,r)) add(cnt<<1|1,mid+1,nr,l,r,a); sum[cnt]=sum[cnt<<1]+sum[cnt<<1|1]; } ll query(ll cnt,ll nl,ll nr,ll l,ll r) { if(cover(nl,nr,l,r)) { return sum[cnt]; } pushdown(cnt,nl,nr); ll mid=(nl+nr)>>1; ll ans=0; if(intersection(nl,mid,l,r)) ans+=query(cnt*2,nl,mid,l,r); if(intersection(mid+1,nr,l,r)) ans+=query(cnt*2+1,mid+1,nr,l,r); return ans; } ll n,m; int main() { cin>>n>>m; for(ll i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(m--) { ll k; scanf("%lld",&k); if(k==1) { ll l,r,t; scanf("%lld%lld%lld",&l,&r,&t); add(1,1,n,l,r,t); } else if(k==2) { ll l,r; scanf("%lld%lld",&l,&r); printf("%lld\n",query(1,1,n,l,r)); } } }