近日受到微軟編程之美大賽第二題和hdu一些題目變態般的大數據的刺激,而且老是聽到群里的一些大神講什么線段樹,樹狀數組,分桶法呀等等一系列不明覺厲的東西,花了幾天好好看了下線段樹和樹狀數組,下面我來分享一些,我的心得和感悟,如有不足之處歡迎大神們前來狂噴。
微軟編程之美初賽第一場樹題解http://blog.csdn.net/asdfghjkl1993/article/details/24306921
線段樹和樹狀數組都是一種擅長處理區間的數據結構。它們間最大的區別之一就是線段樹是一顆完美二叉樹,而樹狀數組(BIT)相當於是線段樹中每個節點的右兒子去掉。
如圖:
線段樹
樹狀數組:
樹狀數組一般適用於三類問題:
1,修改一個點求一個區間
2,修改一個區間求一個點
3,求逆序列對
而用樹狀數組能夠解決的問題,用線段樹肯定能夠解決,反之則不一定。但是樹狀數組有一個明顯的好處就是較為節省空間,實現要比線段樹要容易得多,而且在處理某些問題的時候使用樹狀數組效率反而會高得多。 昨天看到某位大牛在博客上也留下了這樣一句話,線段樹擅長處理橫向區間的問題,樹狀數組擅長處理縱向區間的問題,可能由於水平有限,暫時還木有體會到這一點。。。。憂傷。。。
下面我們來看兩道比較基礎的線段樹模板題
首先是點修改的:
一次修改一個點,然后查詢最大值還有和:
1 void update(int u,int v,int o,int l,int r) 2 3 { 4 5 int m=(l+r)/2; 6 7 if(l==r) 8 9 { 10 11 maxv[o]=v; 12 13 sum[o]=v; 14 15 } 16 17 else 18 19 { 20 21 if(u<=m) 22 23 update(u,v,o*2,l,m); 24 25 else 26 27 update(u,v,o*2+1,m+1,r); 28 29 maxv[o]=max(maxv[o*2],maxv[o*2+1]); 30 31 sum[o]=sum[o*2]+sum[o*2+1]; 32 33 } 34 35 } 36 37 int query_sum(int ql,int qr,int o,int l,int r) 38 39 { 40 41 int m=(l+r)/2; 42 43 if(ql<=l&&r<=qr) 44 45 return sum[o]; 46 47 if(ql<=m) 48 49 return query_sum(ql,qr,o*2,l,m); 50 51 if(m<qr) 52 53 return query_sum(ql,qr,o*2+1,m+1,r); 54 55 } 56 57 int query_max(int ql,int qr,int o,int l,int r) 58 59 { 60 61 int m=(l+r)/2,ans=-1; 62 63 if(ql<=l&&r<=qr) 64 65 return maxv[o]; 66 67 if(ql<=m) 68 69 return max(ans,query_max(ql,qr,o*2,l,m)); 70 71 if(m<qr) 72 73 return max(ans,query_max(ql,qr,o*2+1,m+1,r)); 74 75 }
然后是區間修改的:
Uva11992這道題是劉汝佳厚白書中的例題
題目鏈接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3143
大意為對一個矩陣進行操作,選擇其中子矩陣(x1,y1,x2,y2)可以讓它每個元素增加v
也可以讓它每個元素等於v,也可以查詢這個子矩陣的元素和,最小值,最大值。
解決方法當然是線段樹,不過對於這棵線段樹的update,對於set操作要請除節點上的
Addv標記,但對於add操作不清楚setv標記,在maintain函數中先考慮setv再考慮addv
而在query中要綜合考慮setv和addv.
1 #include<iostream> 2 3 #include<cstdio> 4 5 #include<cstring> 6 7 #include<algorithm> 8 9 using namespace std; 10 11 12 13 const int maxnode = 1<<17; 14 15 16 17 int _sum, _min, _max, op, x1, x2, y1, y2, x, v; 18 19 20 21 class IntervalTree { 22 23 int sumv[maxnode], minv[maxnode], maxv[maxnode], setv[maxnode], addv[maxnode]; 24 25 26 27 // 維護節點o 28 29 void maintain(int o, int L, int R) { 30 31 int lc = o*2, rc = o*2+1; 32 33 if(R > L) { 34 35 sumv[o] = sumv[lc] + sumv[rc]; 36 37 minv[o] = min(minv[lc], minv[rc]); 38 39 maxv[o] = max(maxv[lc], maxv[rc]); 40 41 } 42 43 if(setv[o] >= 0) { minv[o] = maxv[o] = setv[o]; sumv[o] = setv[o] * (R-L+1); } 44 45 if(addv[o]) { minv[o] += addv[o]; maxv[o] += addv[o]; sumv[o] += addv[o] * (R-L+1); } 46 47 } 48 49 50 51 //標記傳遞 52 53 void pushdown(int o) { 54 55 int lc = o*2, rc = o*2+1; 56 57 if(setv[o] >= 0) { 58 59 setv[lc] = setv[rc] = setv[o]; 60 61 addv[lc] = addv[rc] = 0; 62 63 setv[o] = -1; // 清楚標記 64 65 } 66 67 if(addv[o]) { 68 69 addv[lc] += addv[o]; 70 71 addv[rc] += addv[o]; 72 73 addv[o] = 0; // Çå³ý±¾½áµã±ê¼Ç 74 75 } 76 77 } 78 79 80 81 void update(int o, int L, int R) { 82 83 int lc = o*2, rc = o*2+1; 84 85 if(y1 <= L && y2 >= R) { // 在區間內 86 87 if(op == 1) addv[o] += v; 88 89 else { setv[o] = v; addv[o] = 0; } 90 91 } else { 92 93 pushdown(o); 94 95 int M = L + (R-L)/2; 96 97 if(y1 <= M) update(lc, L, M); else maintain(lc, L, M); 98 99 if(y2 > M) update(rc, M+1, R); else maintain(rc, M+1, R); 100 101 } 102 103 maintain(o, L, R); 104 105 } 106 107 108 109 void query(int o, int L, int R, int add) { 110 111 if(setv[o] >= 0) { 112 113 int v = setv[o] + add + addv[o]; 114 115 _sum += v * (min(R,y2)-max(L,y1)+1); 116 117 _min = min(_min, v); 118 119 _max = max(_max, v); 120 121 } else if(y1 <= L && y2 >= R) { 122 123 _sum += sumv[o] + add * (R-L+1); 124 125 _min = min(_min, minv[o] + add); 126 127 _max = max(_max, maxv[o] + add); 128 129 } else { 130 131 int M = L + (R-L)/2; 132 133 if(y1 <= M) query(o*2, L, M, add + addv[o]); 134 135 if(y2 > M) query(o*2+1, M+1, R, add + addv[o]); 136 137 } 138 139 } 140 141 }; 142 143 144 145 const int maxr = 20 + 5; 146 147 const int INF = 1000000000; 148 149 150 151 IntervalTree tree[maxr]; 152 153 154 155 int main() { 156 157 int r, c, m; 158 159 while(scanf("%d%d%d", &r, &c, &m) == 3) { 160 161 memset(tree, 0, sizeof(tree)); 162 163 for(x = 1; x <= r; x++) { 164 165 memset(tree[x].setv, -1, sizeof(tree[x].setv)); 166 167 tree[x].setv[1] = 0; 168 169 } 170 171 while(m--) { 172 173 scanf("%d%d%d%d%d", &op, &x1, &y1, &x2, &y2); 174 175 if(op < 3) { 176 177 scanf("%d", &v); 178 179 for(x = x1; x <= x2; x++) tree[x].update(1, 1, c); 180 181 } else { 182 183 _sum = 0; _min = INF; _max = -INF; 184 185 for(x = x1; x <= x2; x++) tree[x].query(1, 1, c, 0); 186 187 printf("%d %d %d\n", _sum, _min, _max); 188 189 } 190 191 } 192 193 } 194 195 return 0; 196 197 }
再來看看樹狀數組的
先來個改點求區間的
看看hdu1161
題目鏈接:
http://acm.hdu.edu.cn/showproblem.php?pid=1166
題目大意:給n個初始數據構建一棵樹狀數組,然后進行查詢求和等一些列操作。
標准模板題,不解釋。

1 #include<iostream> 2 3 #include<algorithm> 4 5 #include<cstring> 6 7 #include<cstdio> 8 9 #include<cmath> 10 11 using namespace std; 12 13 const int MAX=50005; 14 15 int N; 16 17 class BIT 18 19 { 20 21 private: 22 23 int bit[MAX]; 24 25 int lowbit(int t) 26 27 { 28 29 return t&-t; 30 31 } 32 33 public: 34 35 BIT() 36 37 { 38 39 memset(bit,0,sizeof(bit)); 40 41 } 42 43 int sum(int i) 44 45 { 46 47 int s=0; 48 49 while(i>0) 50 51 { 52 53 s+=bit[i]; 54 55 i-=lowbit(i); 56 57 } 58 59 return s; 60 61 } 62 63 void add(int i,int v) 64 65 { 66 67 while(i<=N) 68 69 { 70 71 bit[i]+=v; 72 73 i+=lowbit(i); 74 75 } 76 77 } 78 79 }; 80 81 int main() 82 83 { 84 85 int T; 86 87 while(cin>>T) 88 89 { 90 91 for(int t=1;t<=T;t++) 92 93 { 94 95 printf("Case %d:\n",t); 96 97 cin>>N; 98 99 BIT tree; 100 101 for(int i=1;i<=N;i++) 102 103 { 104 105 int x; 106 107 cin>>x; 108 109 tree.add(i,x); 110 111 } 112 113 char ord[15]; 114 115 while(scanf("%s",ord)&&strcmp(ord,"End")) 116 117 { 118 119 int a,b; 120 121 scanf("%d%d",&a,&b); 122 123 switch(ord[0]) 124 125 { 126 127 case 'Q': 128 129 printf("%d\n",tree.sum(b)-tree.sum(a-1)); 130 131 break; 132 133 case 'A': 134 135 tree.add(a,b); 136 137 break; 138 139 case 'S': 140 141 tree.add(a,-b); 142 143 break; 144 145 } 146 147 } 148 149 } 150 151 } 152 153 return 0; 154 155 }
再看一道修改區間,然后單點查詢的
看hdu 1556
題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1556
N個氣球排成一排,從左到右依次編號為1,2,3....N.每次給定2個整數a b(a <= b),lele便為騎上他的“小飛鴿"牌電動車從氣球a開始到氣球b依次給每個氣球塗一次顏色。但是N次以后lele已經忘記了第I個氣球已經塗過幾次顏色了,你能幫他算出每個氣球被塗過幾次顏色嗎?
這題是修改區間的,單點查詢的,則要注意一點 先對左區間進行操作add(a,1),然后對右邊區間進行操作add(b+1,-1),把不該修改的那部分值再修改回來,即實現了對一個區間的值的修改。然后通過sum(i),即可求點(如果有人問為什么是sum(i)而不是bit[i]呢?我只能說你太天真了。。。。自己再紙上畫畫就能知道。。。。)

1 #include<iostream> 2 3 #include<algorithm> 4 5 #include<cstdio> 6 7 #include<cstring> 8 9 using namespace std; 10 11 const int MAX=100001; 12 13 int N; 14 15 class BIT2 16 17 { 18 19 private: 20 21 int bit[MAX]; 22 23 int lowbit(int t) 24 25 { 26 27 return t&-t; 28 29 } 30 31 public: 32 33 BIT2() 34 35 { 36 37 memset(bit,0,sizeof(bit)); 38 39 } 40 41 int add(int i,int v) 42 43 { 44 45 while(i<=N) 46 47 { 48 49 bit[i]+=v; 50 51 i+=lowbit(i); 52 53 } 54 55 } 56 57 int sum(int i) 58 59 { 60 61 int s=0; 62 63 while(i>0) 64 65 { 66 67 s+=bit[i]; 68 69 i-=lowbit(i); 70 71 } 72 73 return s; 74 75 } 76 77 }; 78 79 int main() 80 81 { 82 83 while(cin>>N&&N) 84 85 { 86 87 int a,b; 88 89 BIT2 tree; 90 91 for(int i=1;i<=N;i++) 92 93 { 94 95 scanf("%d%d",&a,&b); 96 97 tree.add(a,1); 98 99 tree.add(b+1,-1); 100 101 } 102 103 for(int i=1;i<=N;i++) 104 105 { 106 107 if(i!=1) cout<<" "; 108 109 printf("%d",tree.sum(i)); 110 111 } 112 113 cout<<endl; 114 115 } 116 117 return 0; 118 119 } 120 121
再看一道二維的
Hdu1892
http://acm.hdu.edu.cn/showproblem.php?pid=1892
跟一維主要的區別

1 void init() 2 3 { 4 5 for(int i=1;i<MAX;i++) 6 7 for(int j=1;j<MAX;j++) 8 9 { 10 11 d[i][j]=1; 12 13 c[i][j]=lowbit(i)*lowbit(j); 14 15 } 16 17 } 18 19 int sum(int i,int j) 20 21 { 22 23 int tot=0; 24 25 for(int x=i;x>0;x-=lowbit(x)) 26 27 for(int y=j;y>0;y-=lowbit(y)) 28 29 { 30 31 tot+=c[x][y]; 32 33 } 34 35 return tot; 36 37 } 38 39 void add(int i,int j,int v) 40 41 { 42 43 for(int x=i;x<MAX;x+=lowbit(x)) 44 45 for(int y=j;y<MAX;y+=lowbit(y)) 46 47 { 48 49 c[x][y]+=v; 50 51 } 52 53 }