線段樹模板及總結
焦作一中信息學 oy
在信息學競賽中,經常遇到這樣一類問題:這類問題通常可以建模成數軸上的問題或是數列的問題,具體的操作一般是每次對數軸上的一個區間或是數列中的連續若干個數進行一種相同的處理。常規的做法一般依托於線性表這種數據結構,導致了處理只能針對各個元素逐個進行,因此算法的效率較低。
線段樹是一種能夠有效處理區間操作的高級數據結構,利用這種數據結構,我們能夠設計出針對上述問題更加高效的算法。
線段樹的題目通常比較明顯,一般一個很明顯的特征是m次對某一區間長度的查詢。或者是修改。所以我們通常需要的只是將線段樹的模型稍加修改,進而套入題目中即可。
模板:
對單個點的修改,和對一段區間的查詢:

1 #include<iostream> 2 #include<iomanip> 3 #include<cstring> 4 #include<climits> 5 #include<cmath> 6 #include<cstdio> 7 #include<cstdlib> 8 #include<queue> 9 #include<vector> 10 #include<map> 11 #include<algorithm> 12 #include<string> 13 #include<memory> 14 using namespace std; 15 16 const int e=100006; 17 struct qq 18 { 19 int maxx; 20 }tree[4*e];//線段樹要開4倍的點的個數 21 int n,t,a,b; 22 23 void updata(int l,int r,int root) 24 { 25 if(r<a || l>a) return; 26 if(r==l) 27 { 28 tree[root].maxx=b; 29 return; 30 } 31 32 int mid=(l+r)/2; 33 updata(l,mid,root*2); 34 updata(mid+1,r,root*2+1); 35 tree[root].maxx=max(tree[root*2].maxx,tree[root*2+1].maxx); 36 } 37 38 39 int search(int l,int r,int root) 40 { 41 if(l>b || r<a) return(-999999999); 42 43 if(l>=a && r<=b) return(tree[root].maxx); 44 45 int mid=(l+r)/2; 46 return(max( search(l,mid,root*2), search(mid+1,r,root*2+1))); 47 } 48 49 int main() 50 { 51 freopen("in.in","r",stdin); 52 53 memset(tree,0,sizeof(tree)); 54 cin>>n; 55 56 for(int i=0;i<n;i++) 57 { 58 scanf("%d%d%d",&t,&a,&b); 59 60 if(t==1) updata(1,n,1);//將點a的值改為b 61 if(t==2) cout<< search(1,n,1) << endl;//查找區間a(含)到b(含)的最大值; 62 } 63 64 fclose(stdin); fclose(stdout); 65 return 0; 66 }
對一段區間的修改和查詢:

1 #include<iostream> 2 #include<iomanip> 3 #include<cstring> 4 #include<climits> 5 #include<cmath> 6 #include<cstdio> 7 #include<cstdlib> 8 #include<queue> 9 #include<vector> 10 #include<map> 11 #include<algorithm> 12 #include<string> 13 #include<memory> 14 using namespace std; 15 16 const int e=100006; 17 struct qq 18 { 19 int maxx,delta; 20 }tree[4*e];//線段樹要開4倍的點的個數 21 int n,t,a,b; 22 23 void updata(int l,int r,int root)//更新數據 24 { 25 if(b<l || r<a) return; 26 27 if(l>=a && r<=b)//這句是核心,若當前區間包含於修改區間,就不往下傳,(未傳到葉子節點); 28 { 29 tree[root].maxx++; 30 tree[root].delta++; 31 return; 32 } 33 34 int mid=(l+r)/2,delta=tree[root].delta; 35 tree[root*2].maxx+=delta; tree[root*2].delta+=delta; 36 tree[root*2+1].maxx+=delta; tree[root*2+1].delta+=delta; 37 tree[root].delta=0;//這句很關鍵,根的偏移量傳遞到子樹后清零 38 updata(l,mid,root*2); 39 updata(mid+1,r,root*2+1); 40 tree[root].maxx=max( tree[root*2].maxx, tree[root*2+1].maxx); 41 return; 42 } 43 44 int search(int l,int r,int root) 45 { 46 if(l>b || r<a) return(-99999999); 47 48 if(l>=a && r<=b) return(tree[root].maxx); 49 50 int mid=(l+r)/2,delta=tree[root].delta; 51 tree[root*2].maxx+=delta; tree[root*2].delta+=delta; 52 tree[root*2+1].maxx+=delta; tree[root*2+1].delta+=delta; 53 tree[root].delta=0;//這句很關鍵,根的偏移量傳遞到子樹后清零 54 return(max( search(l,mid,root*2), search(mid+1,r,root*2+1))); 55 } 56 57 int main() 58 { 59 //freopen("in.in","r",stdin); 60 61 memset(tree,0,sizeof(tree)); 62 cin>>n; 63 64 for(int i=0;i<n;i++) 65 { 66 scanf("%d%d%d",&t,&a,&b); 67 68 if(t==1) updata(1,n,1); 69 if(t==2) cout<< search(1,n,1) << endl; 70 } 71 72 fclose(stdin); fclose(stdout); 73 return 0; 74 }
以下是做線段樹題目時的易錯點:
1、開線段樹的結構體時一定要開到4倍的點的大小;
2、寫區間求和時的題目時search()函數的返回值在不再區間里時返回0,而區間求最小值時返回999999999,最大值時返回-999999999;(int時)一定要是九個9,不然有些極限數據會卡范圍;
3、寫修改單個點的值和一個區間的值的函數最好分開,一個是節省時間,還有可以防止代碼混亂而出錯;
4、Search()&updata()函數中判斷區間范圍的if語句中l,r和查找的區間a,b的關系容易出錯;
5、注意根節點和子節點的關系,特別是+1的問題,歸結為一句話就是:若加都加,若不加都不加即:
mid=(l+r);
左子樹:l~mid(不加一),根為root*2;
右子樹:r~mid+1(加一了),根為root*2+1(也加一);
6、權值是在邊上還是點上,這兩種關系代碼判斷上有不同;
7、結構體賦初值的時候也要注意,根據所求的是最大還是最小還是和來判斷;
8、線段樹的左端點在數組中的下標一定要是1,而不是0。
以后如果發現問題,還會繼續補充......