前端也要學點資料結構 神奇的樹狀陣列的三大應用

2021-09-08 11:19:55 字數 3591 閱讀 9899

前文我們**了樹狀陣列的原理。樹狀陣列就是一種資料結構,它天生用來維護陣列的字首和,從而可以快速求得某乙個區間的和,並支援對元素的值進行修改。但是樹狀陣列並非只有這一種功能,變形後它還能衍生出兩個功能,本文我們就來分別討論下樹狀陣列這三大功能。

永遠要記住,基本的樹狀陣列維護的是陣列的字首和,所有的區間求值都可以轉化成用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...