前文我們**了樹狀陣列的原理。樹狀陣列就是一種資料結構,它天生用來維護陣列的字首和,從而可以快速求得某乙個區間的和,並支援對元素的值進行修改。但是樹狀陣列並非只有這一種功能,變形後它還能衍生出兩個功能,本文我們就來分別討論下樹狀陣列這三大功能。
永遠要記住,基本的樹狀陣列維護的是陣列的字首和,所有的區間求值都可以轉化成用sum[m]-sum[n-1]
來解,這點無論是在改點還是接下來要說的改段中都非常重要。
這也是樹狀陣列的基本應用。我們可以來看一下這道題 敵兵布陣。
如果看了前文 【前端也要學點資料結構】 神奇的樹狀陣列,解法也就呼之欲出了,直接給出**:
#include#include#include#includeusing namespace std;
#define n 50005
int lowbit(int x)
int sum[n], cnt;
void update(int index, int val)
int getsum(int index)
int main()
printf("case %d:\n", cas++);
while (cin >> str)
} return 0;
}
改段求點和改點求段恰好相反,比如有乙個陣列a = [x, 0, 0, 0, 0, 0, 0, 0, 0, 0]
,每次的修改都是一段,比如讓a[1]~a[5]
中每個元素都加上10,讓a[6]~a[9]
中每個元素都減去2,求任意的元素的值。
看例題 color the ball
跟改點求段不同,這裡要轉變乙個思想。在改點求段中,sum[i]表示ci節點所管轄的子節點的元素和,而在改段求點中,sum[i]表示ci所管轄子節點的批量統一增量。
還是看這個經典的圖:
比方說,c8管轄a1~a8這8個節點,如果a1~a8每個都染色一次,因為前面說了sum[i]表示i所管轄子節點的統一增量,那麼也就是sum[8]+=1
,a5~a7都染色兩次,也就是sum[6] +=2, sum[7] +=2
。如果要求a1被染色的次數,c8是能管轄到a1的,也就是說sum[8]的值和a1被染色的次數有關,仔細想想,也就是把能管轄到a1的父節點的sum值累積起來即可。兩個過程正好和改點求段相反。
完整**:
#include#include#include#includeusing namespace std;
#define n 100005
int sum[n], n;
int lowbit(int x)
void update(int index, int val)
}int query(int index)
return ans;
}int main()
for (int i = 1; i < n; i++)
printf("%d ", query(i));
printf("%d\n", query(n));
} return 0;
}
改段求段也有道經典的模板題:a ****** problem with integers
我們還是從簡單的例子入手,比如有如下陣列(a[1]=1,..a[9]=9):
1 2 3 4 5 6 7 8 9 10
假設我們將a[1]~a[4]
這段增加5,對於我們要求的區間和來說,要麼是[1,2]
這種屬於所改段的子區間,要麼是[1,8]
這種屬於所改段的父區間(前面說了,所有的區間求值都可以用sum[m]-sum[n-1]來解,所以我們只考慮字首和),我們分別討論。
如果所求是類似[1,8]
這種,我們可以很開心地發現,我們將區間增量(4*5)全部加在a[4]
這個元素上,對結果並沒有什麼影響!於是變成了一般的改點求段。
如果所求是類似[1,2]
這種,我們可以用類似改段求點中染色的思想進行處理。譬如[1,4]
成段加5,如果我們要計算[1,2]
的和。我們將[1,3]
進行「染色」(節點4加上了4*5的權重),因為[1,3]
在樹狀陣列的劃分中可以分為兩個區間,[1,2]
和[3,3]
,所以我們用類似改段求點對這兩塊區域進行「染色」,染上的次數為5。我們要求的是[1,2]
的區間和,我們只需找2
被染色的次數,因為[1,n]
進行染色。如果m(1<=m<=n)被染色,那麼m的右邊肯定都被染色了。求出被染色的次數,然後乘上區間寬度,就是整段的和了。
這樣我們分別對兩種情況進行了處理,更重要的是,這兩種情況互不影響! 於是我們簡單地把兩個結果相加就ok了,而這兩個過程,分別正是改點求段和改段求點!
完整**:
#include#include#includeusing namespace std;
#define n 100005
#define ll __int64
ll b[n], c[n];
int n;
int lowbit(int x)
void update_backwards(int index, ll val)
void update_forward(int index, ll val)
void update(int index, ll val)
ll query_forward(int index)
ll query_backwards(int index)
ll query(int index)
//---------------- main -------------- //
int main()
while (t--) else
} return 0;
}
這裡有一點需要注意:一般的用陣列陣列來解的題,都是不用a[0]的,也就是元素是從a[1]~a[n],因為sum[n~m]=sum[m]-sum[n-1]
,避免n-1
為負數。而本題中的改段求段中的元素是從a[2]~a[n+1]
,因為update()
函式中的子函式update_forward()
函式中index-1
不能為負,所以引數index
最小是1,所以sum[n-1]
中n-1
最小是1,所以n最小是2,所以元素下標必須從2
開始。 前端也要學點資料結構 神奇的樹狀陣列
最近在學習位運算,正好把樹狀陣列總結下,也算是能正式給data structure建個分類。那麼,樹狀陣列到底有什麼用呢?誠然,一樣沒什麼卵用的東西我們學它幹嘛。下面舉個樹狀陣列的經典應用 區間求和。假設我們有如下陣列 陣列元素從index 1開始 var a x,1,2,3,4,5,6,7,8,9...
資料結構 樹狀陣列的幾種用法
單點修改 區間修改 單點查詢 區間查詢 洛谷p3374 洛谷p3368 洛谷p3374 includeusing namespace std intn,m int tree 2000010 int lowbit int k void add int x,intk int sum int x retu...
資料庫中樹狀資料結構的設計
img img 乙個cms頻道分類設計,大體意思是通過自定義id的方式。id 步長 2位 那麼一級分類為 01 99 varchar 20 2 10 那麼id 01 99999999999999999999 如果你覺得分類不夠用的可以加大步長,級次關係 img quote select schann...