比較簡單的線段樹入門
線段樹是一種十分方便的數據結構,可以解決多段連續區間的查詢問題
對比其他一些數據結構,線段樹能夠解決的問題是動態的,這也是線段樹的特性
線段樹的性質還有每個節點保存一個線段,以及左節點保存的線段是父節點保存的線段的左半段,右子節點反之
(即當父節點保存的線段為[1,n],左子節點保存的線段為[1,(1+n)/2],而右子節點保存的線段為[(1+n)/2+1,n])
由於線段樹是一顆完全二叉樹,所以每個操作的復雜度大概保持在O(log n)
線段樹可以做到的操作有如下:
1)構建線段樹;
2)區間查詢;
3)區間或單點修改;
(單點查詢即為左右端點相等的特殊區間查詢,單點修改也是同理)
那話不多說(已經不少了),接下來就對以上幾種操作舉例說明(這裏統一以維護區間的和為例)
1)線段樹的構建
就是遞歸構造一個樹結構
1 int a[200010];//a[i]記錄數組 2 struct node{int val;}tree[400010];//tree用於構造線段樹 3 //root 當前父節點 4 //l 當前節點保存區間的左端點 5 //r 當前節點保存區間的右端點 6 void bdt(int root,int l,int r) 7 { 8 if(l==r) //當節點保存的區間是一個點時,記錄該元素 9 {tree[root].val=a[l]; return;} 10 //遞歸構建子樹 11 bdt(rt*2,l,(l+r)/2); 12 bdt(rt*2+1,(l+r)/2+1,r); 13 //將左右子節點的值回溯到當前節點上 14 tr[rt].val=tr[rt*2].val+tr[rt*2+1].val; 15 }
2)區間查詢
區間的查詢就是將要查詢的區間劃分為線段樹上節點保存的區間,然後通過節點的合成得到目的區間的值
1 //root,l,r同上 2 //f 查詢目標區間的左端點 3 //t 查詢目標區間的右端點 4 int query(int root,int l,int r,int f,int t) 5 { 6 //如果查詢區間與節點區間沒有交集則返回0(0對求和沒有影響) 7 if(t<l || f>r)8 return 0; 9 //如果節點區間包含於查詢區間則返回當前區間的求和值 10 if(f<=l && t>=r) 11 return tree[root].val; 12 //左右子樹遞歸將求和保存到父節點 13 return query(root*2,l,(l+r)/2,f,t)+query(root*2+1,(l+r)/2+1,r,f,t); 14 }
可以見得,由於所選的區間盡量少,所以很大程度上節省了時間復雜度,大概節省到了O(log n)
還要記得使用線段樹解決問題有一個條件——問題是可以分解解決的
3)單點及區間的修改
首先是單點修改
1 //root,l,r仍然同上 2 //k 要修改的點 3 //add 要增加的值 4 void update(int root,int l,int r,int k,int add) 5 { 6 //當節點是葉子節點執行修改 7 if(l==r) 8 {if(k==l) tree[root]+=ad; return;} 9 //遞歸左右字數尋找目標節點 10 int mid=(l+r)/2; 11 if(k<=mid) 12 update(root*2,l,mid,k,add); 13 else update(root*2+1,mid+1,r,k,add); 14 //回溯更新父節點(可見回溯在線段樹中還是很重要的) 15 tree[root]=tree[root*2]+tree[root*2+1]; 16 }
然後是區間修改
區間修改要是也和上面一樣那就失去了線段樹的意義了(笑
於是就需要一個很有意思的操作——對每個點維護一個延遲標記
這個標記可以記錄節點受到了什麽樣的修改,而這個修改會影響他的子節點
當我們找到一個節點並且判斷需要考慮其子節點,就將這個節點的延遲標記向子節點傳遞,並將這個節點的標記清零
恩...由於這個操作新加入了一個元素,就破壞了以上代碼的連續性,這裏決定直接上例題(cogs上的一道模板題)
1317. 數列操作c
★★☆ 輸入文件:shuliec.in
輸出文件:shuliec.out
簡單對比
時間限制:1 s
內存限制:128 MB
所有答案小於4611686018427387904
【問題描述】
假設有一列數 {Ai }(1 ≤ i ≤ n),n<=100000 ,支持如下兩種操作:
(1)將 A i至A j 的值均增加 D 。( i,j,D 是輸入的數)
(2) 輸出 A s +A s+1 +…+A t 。( s, t 都是輸入的數, S ≤ T )
根據操作要求進行正確操作並輸出結果。
【輸入格式】
輸入文件第一行一個整數 n ,
第二行為 n 個整數,表示 {A i } 的初始值。
第三行為一個整數 m ,表示操作數
v 下接 m 行,每行描述一個操作,有如下兩種情況:
ADD i j d ( 表示將 A i至A j 的值均增加 D , 1<=i,j<=n , d 為整數 )
SUM s t (表示輸出 A s +…+A t )
【輸出格式】
對於每一個 SUM 提問,輸出結果
【輸入輸出樣例】
輸入:
shuliec.in
4
1 4 2 3
3
SUM 1 3
ADD 2 2 50
SUM 2 3
輸出:
shuliec.out
7
56
思路:就是一道區間修改區間查詢的線段樹模板,以下代碼
1 #include<cstdio> 2 #define LL long long 3 using namespace std; 4 int n,m,a[200010]; 5 struct node{LL val,mark;}tree[400010];//這裏的mark即為延遲標記 6 char s[10]; 7 void buildtree(int root,int l,int r) 8 { 9 //構建時要先把所有點的延遲標記清零 10 tree[root].mark=0; 11 if(l==r) 12 {tree[root].val=a[l]; return;} 13 buildtree(root*2,l,(l+r)/2); 14 buildtree(root*2+1,(l+r)/2+1,r); 15 tree[root].val=tree[root*2].val+tree[root*2+1].val; 16 } 17 //這就是將延遲標記向下傳遞的操作 18 void pushdown(int root,int x) 19 { 20 if(tree[root].mark!=0) 21 { 22 //考慮到可能有些節點並不需要繼續向下查詢,應該是"+=" 23 tree[root*2].mark+=tree[root].mark; 24 tree[root*2+1].mark+=tree[root].mark; 25 //這可是個區間啊,每個節點的權值只增加標記值怎麽能行 26 tree[root*2].val+=tree[root].mark*(x-(x>>1)); 27 tree[root*2+1].val+=tree[root].mark*(x>>1); 28 }tree[root].mark=0; 29 } 30 LL query(int root,int l,int r,int f,int t) 31 { 32 if(t<l || f>r) 33 return 0; 34 if(f<=l && t>=r) 35 return tree[root].val; 36 pushdown(root,r-l+1); 37 return query(root*2,l,(l+r)/2,f,t)+query(root*2+1,(l+r)/2+1,r,f,t); 38 } 39 //備受期待的區間修改,前三個元素仍然是同上 40 //f,t分別是修改區間的左右端點,v則是需要增加的值 41 void add(int root,int l,int r,int f,int t,int v) 42 { 43 int mid=(l+r)/2; 44 //修改區間和節點區間沒有交集,自然不考慮 45 if(f>r || t<l) return; 46 //節點區間包含於修改區間,更改區間權值,記錄延遲標記 47 if(f<=l && t>=r) 48 { 49 tree[root].mark+=v; 50 tree[root].val+=(LL)v*(r-l+1); 51 return; 52 } 53 //將延遲標記向下傳遞 54 pushdown(root,(r-l+1)); 55 //考慮和左子,右子是否有交集的情況 56 add(root*2,l,mid,f,t,v); 57 add(root*2+1,mid+1,r,f,t,v); 58 //重要的回溯 59 tree[root].val=tree[root*2].val+tree[root*2+1].val; 60 } 61 int main() 62 { 63 int x,y,w,i,j; 64 LL ans; 65 scanf("%d",&n); 66 for(i=1;i<=n;++i) 67 scanf("%d",&a[i]); 68 buildtree(1,1,n); 69 scanf("%d",&m); 70 for(i=1;i<=m;++i) 71 { 72 scanf("%s",s); 73 if(s[0]==‘S‘) 74 { 75 scanf("%d%d",&x,&y); 76 ans=query(1,1,n,x,y); 77 printf("%lld\n",ans); 78 } 79 if(s[0]==‘A‘) 80 { 81 scanf("%d%d%d",&x,&y,&w); 82 add(1,1,n,x,y,w); 83 } 84 } 85 }
比較簡單的線段樹入門