【線段樹】【P3372】模板-線段樹
百度百科
Definition&Solution
線段樹是一種log級別的樹形結構,可以處理區間修改以及區間查詢問題。期望情況下,復雜度為O(nlogn)。
核心思想見百度百科,線段樹即將每個線段分成左右兩個線段做左右子樹。一個線段沒有子樹,當且僅當線段表示的區間為[a,a]。
由於編號為k的節點的子節點為2k以及2k+1,線段樹可以快速的遞歸左右葉節點。
lazy標記:當進行區間修改的時候,如果一個區間整體全部被包含於要修改的區間,則可以將該區間的值修改後,將lazy標記打在區間上,不再遞歸左右區間。
例如,要修改[15,30]區間整體+2,當前區間為[16,24],被包含於要修改的區間。記代表區間[16,24]的節點編號為k,則tree[k]+=2*(24-16+1),同時lazy[k]+=2。
在下次修改或查詢到k節點時,進行lazy的下放,即如下代碼
inline void Free(cl l,cl r,cl p) { ll m=(l+r)>>1,dp=p<<1; tree[dp]+=(m-l+1)*lazy[p];tree[dp+1]+=(r-m)*lazy[p]; lazy[dp]+=lazy[p];lazy[dp+1]+=lazy[p]; lazy[p]=0; }
註意:被打上lazy標記的區間實際上已經修改完區間和,每次free修改的是子區間。
Example
傳送門
Description
已知一個數列,你需要進行下面兩種操作:
1.將某區間每一個數加上x
2.求出某區間每一個數的和
Input
第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。
第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。
接下來M行每行包含3或4個整數,表示一個操作,具體如下:
操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k
操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和
Output
輸出包含若幹行整數,即為所有操作2的結果。
Sample Input
5 5 1 5 4 2 3 2 2 41 2 3 2 2 3 4 1 1 5 1 2 1 4
Sample Output
11 8 20
Hint
時空限制:1000ms,128M
數據規模:
對於30%的數據:N<=8,M<=10
對於70%的數據:N<=1000,M<=10000
對於100%的數據:N<=100000,M<=100000
Solution
模板題。有一些需要註意的地方會在summary寫明
Code
#include<cstdio> #define maxn 100010 #define maxt 400010 #define ll long long int #define cl const long long int inline void qr(long long &x) { char ch=getchar();long long f=1; while(ch>‘9‘||ch<‘0‘) { if(ch==‘-‘) f=-1; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x*=f; return; } inline long long max(const long long &a,const long long &b) {if(a>b) return a;else return b;} inline long long min(const long long &a,const long long &b) {if(a<b) return a;else return b;} inline long long abs(const long long &x) {if(x>0) return x;else return -x;} inline void swap(long long &a,long long &b) { long long c=a;a=b;b=c;return; } ll n,m,MU[maxn],sign,a,b,c; ll tree[maxt],lazy[maxt]; void build(const ll l,const ll r,const ll p) { if(l>r) return; if(l==r) {tree[p]=MU[l];return;} ll m=(l+r)>>1,dp=p<<1; build(l,m,dp);build(m+1,r,dp+1); tree[p]=tree[dp]+tree[dp+1]; } inline void Free(cl l,cl r,cl p) { ll m=(l+r)>>1,dp=p<<1; tree[dp]+=(m-l+1)*lazy[p];tree[dp+1]+=(r-m)*lazy[p]; lazy[dp]+=lazy[p];lazy[dp+1]+=lazy[p]; lazy[p]=0; } inline void wohenlan(cl l,cl r,cl p,cl v) {tree[p]+=(r-l+1)*v;lazy[p]+=v;} void add(cl l,cl r,cl p,cl aiml,cl aimr,cl v) { if(l>r) return; if(l>aimr||r<aiml) {return;} if(l>=aiml&&r<=aimr) {wohenlan(l,r,p,v);return;} Free(l,r,p); ll m=(l+r)>>1,dp=p<<1; add(l,m,dp,aiml,aimr,v);add(m+1,r,dp+1,aiml,aimr,v); tree[p]=tree[dp]+tree[dp+1]; } ll ask(cl l,cl r,cl p,cl aiml,cl aimr) { if(l>r) return 0; if(l>aimr||r<aiml) {return 0;} if(l>=aiml&&r<=aimr) {return tree[p];} Free(l,r,p); ll m=(l+r)>>1,dp=p<<1; return ask(l,m,dp,aiml,aimr)+ask(m+1,r,dp+1,aiml,aimr); } int main() { qr(n);qr(m); for(int i=1;i<=n;++i) qr(MU[i]); build(1ll,n,1ll); while(m--) { sign=a=b=0;qr(sign);qr(a);qr(b); if(sign==1) { c=0;qr(c); add(1,n,1,a,b,c); } else printf("%lld\n",ask(1, n, 1, a, b)); } return 0; }
Summary
1、線段樹大小要開4*n。理論上線段樹會有2*n個子節點,但是試試這棵線段樹:1 2 3 4 5
如圖所示:
可以看到,節點數確實是6*2-1=11個,但是由於我們每個節點編號都嚴格按照母節點*2(+1)進行編號,所以我們的編號開到了2*n之外。開4*n是比較保險的。
2、註意對lazy標記的free操作要在確定區間可以再分以後進行。即先寫
if(l>=aiml&&r<=aimr) {wohenlan(l,r,p,v);return;}
或
if(l>=aiml&&r<=aimr) {return tree[p];}
後,如果沒有return,則證明區間一定是可再分的,即還沒有遞歸到葉節點,這時才可以進行free操作。否則的話考慮在葉節點的編號可能大於2*n,我們在葉節點free了一下,標記被下放到了4*n以外……
然後你就炸了。
【線段樹】【P3372】模板-線段樹