樹狀陣列1 樹狀陣列入門

2021-08-18 13:02:42 字數 3501 閱讀 2457

仔細看一下,發現tree的每乙個節點的高度並不是隨意的,而是由它轉成二進位制之後末尾連續零的數量決定的,連續零的數量加1,就是高度,例如:

3->11     零的數量為0,加1等於1,所以它的高度就是1

6->110   零的數量為1,加1等於2,所以它的高度就是2

8->1000 零的數量為3,加1等於4,所以它的高度就是4

我們看一下tree陣列中每乙個數記錄了a中那些數的和:

tree[1]=a[1]

tree[2]=a[1]+a[2]

tree[3]=a[3]

tree[4]=a[1]+a[2]+a[3]+a[4]

tree[5]=a[5]

tree[6]=a[5]+a[6]

tree[7]=a[7]

tree[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]

可以發現,tree中的每乙個節點管理的a都是連續的,具體一點,它管理的數就是在它前面的高度比它大的第乙個tree節點的後乙個到它自己,比如6,前面的比它高的第乙個就是4,4的後乙個是5,它自己是6,所以它管理5~6

那這個「在它前面的高度比它大的第乙個tree節點」怎麼算呢,其實就是它自己轉成二進位制之後去掉最後乙個1(去掉最後乙個1也就是減去最後乙個1所代表的數)之後的數,還是拿6來做個例子,它的二進位制是110,去掉最後乙個1就是100,轉回來就是4了,又比如3,二進位制是11,去掉最後乙個1就是10,也就是2,是不是特別神奇

剛剛我們講到了乙個東西,「最後乙個1」,這個東西怎麼求,這裡就要說到乙個樹狀陣列最重要的函式了,lowbit(),就是用來求最後乙個1的,lowbit()有兩種寫法,都可以求最後乙個1所代表的數,分別是:

(((x-1)&x)^x)

讓我們把它拆開,用乙個例子,看它是怎麼求的:

拿1011000作例子吧

首先,x-1,使得最後的乙個1變為0,末尾的連續零都變成1,1011000-0000001=1010111

然後,和x做&運算,因為末尾原本的連續0變成了連續1,而原本的最後乙個1變成了0,按照與運算的規則,它們都會變成0,但是,原本的最後乙個1的前面的數在x-1後都還是沒有改變的,也就是說,那些位都和原本的x的每一位相同,根據與運算的規則,他們都不會變

1010111

& 1011000

____________

至此,可以發現,x唯一的變化也就是最後乙個1變成了0 ,也就是只有最後乙個1的那一位發生了變化,那麼此時我們只需要讓它與x做一次異或運算,其他位都會變成0,因為它們都一樣,而最後乙個1這一位就會變成1,因為它這一位不一樣,這樣,就可以求出最後乙個1所代表的數了。

((-x)&x)

這種寫法更簡潔,不易記錯,個人推薦這種

首先,我們要知道在計算機中的運算是使用補碼的,想詳細了解補碼的可以看這裡,於是我們可以把((-x)&x)這個運算拆開成這麼幾步:

1、將x的除了符號位的每一位取反

2、將得到的數+1

3、和原本的x做&運算

我們再來將每一步細細分析一下(例子自己試一下就好了哈):

1、將每一位取反之後,原本的x的最後乙個1的位置變成了0,末尾的連續0變成了連續1

2、+1後末尾的連續1又變成了連續0,但是原本的x的最後乙個1的位置又從0變回了1,但是其他的位都還是與原來相反

3、做完&運算之後,除了原本x的最後乙個1的位置還是1之外,其他位都變成了0

lowbit()的分析至此結束

上面講到了求「在它前面的高度比它大的第乙個tree節點」,那麼我們在了解了lowbit()這個函式之後,就可以知道,要求在x前面的高度比它大的第乙個tree節點,就是tree[x-lowbit(x)]

lowbit()還有乙個用法,那就是求tree[x]的父親,x的父親就是比x編號大且比x高度大的第乙個節點,首先,比x編號大,說明它的父親只能由x+某乙個數得到,第乙個節點,說明加的」某乙個數「要盡可能的小,又要比x高度大,說明末尾連續零一定比x多,那麼,滿足要求的,就只有x+lowbit(x)了,仔細看看,是不是滿足裡面的每乙個要求

接下來講一下它基本的兩個操作:

1、單點修改

顯然的(每次看證明之類的東西時最怕看見這三個字了),若a[x]+y,那麼tree[x]也一定會+y,因為每個tree節點都一定管理著相應的a點,那麼當tree[x]+y,tree[x]的父親的值也一定會+y,所以當tree[x]+y,tree[x+lowbit(x)]也要+y,同理,tree[(x+lowbit(x))+lowbit(x+lowbit(x))]也要+y(也就是x的父親的父親),一直加到tree的下標大於n為止(大於n的那一次不加,不然下標會越界)

2、區間查詢

前面講到了,x-lowbit(x)就是在x之前的高度比x大的第乙個節點,因為tree[x]的管理範圍就是x-lowbit(x)+1~x(上面有講),所以,x-lowbit(x)那個節點和x節點的管理範圍一定相鄰且不重合,那麼同理,(x-lowbit(x))-lowbit(x-lowbit(x))那個點(也就是x-lowbit(x)之前的高度比x-lowbit(x)大的第乙個節點)的管理範圍也與x-lowbit(x)節點的管理範圍一定相鄰且不重合,那麼我們就可以得到求1~x區間和的方法,就是每一次用乙個變數sum記錄當前tree[x]的值,然後x-lowbit(x),然後再用sum加上tree[x-lowbit(x)],然後繼續直到x=0為止,這是求1~x的方法,那麼自然,求x~y就是求 1~y - 1~(x-1),那有些人就想到了,為什麼不直接求x~y呢?先從y開始,每次減lowbit(y),直到y=x就停止,這樣不好嗎?不好。你怎麼就能保證y-lowbit(y)最後能夠剛好等於x呢?你總不能在yx時就停止吧!所以,還是安安分分地做吧。

樹狀陣列在這兩個方面的處理還是很優的,時間都只有log(n),但是,用在區間修改上呢?修改一次區間nlog(n),還沒普通的陣列好用,那單點查詢呢?假如要你求x的值,就相當於求1~x - 1~(x-1),用時2log(n),還算不錯,但是,對於區間修改,就沒有更優的方法了嗎?答案自然是有的,那就要用到另乙個神奇的東西了,差分陣列,我們只需要把樹狀陣列用在差分陣列上就可以了,因為我們知道差分陣列的區間修改可以說是所有資料結構裡面最快的了(o(1)的時間,實在找不出能比它快的),但是它的查詢就顯得很弱了,相對的,樹狀陣列的區間修改很弱,但是查詢十分的快,所以,將他們結合一下,就很厲害了,具體怎麼結合呢,見下:

1、區間修改

若用差分陣列做,設b為差分陣列,那麼每次修改只需要將b[l]+x然後b[r+1]-x即可,那麼放到樹狀陣列中,也就是只需要修改tree[l]和tree[r+1]兩個點即可,那麼時間就只需要2log(n)了

2、單點查詢

若用差分陣列做查詢x的值,就是統計b陣列的1~x的和,那麼求字首和這個東西樹狀陣列是很擅長的,只需要log(n)的時間即可

但仔細看看,可以發現,這兩個操作中並不會修改b陣列的值,也就是說,我們並不需要真正去建乙個差分陣列,其實只是運用到它的思想罷了

至此,樹狀陣列講解完畢

樹狀陣列2——更高深的樹狀陣列

樹狀陣列 入門

樹狀陣列,乙個用來區間求和修改都為log n 的演算法。在網上資料很多,看起來也不是很難,學習一下!基本上學習樹狀陣列都會看到下面這個圖,這也是最基本的乙個圖,看懂這個以後,對樹狀陣列也就會有 一定的了解了。令這棵樹的結點編號為c1,c2.cn。令每個結點的值為這棵子樹的值的總和,那麼容易發現 c1...

樹狀陣列入門

用office做了一張pdf 這是一維的情形,如果是二維,可以把每一行的一維樹狀陣列看成乙個節點,然後再把二維樹狀陣列看成一維樹狀陣列。好文章 兩道入門題 對於第一題hdu1556 題意 初始陣列元素值都為0,給定區間 x,y 將區間 x,y 每個元素的值 1,最後輸出整個陣列每個元素的值。更新區間...

樹狀陣列入門

樹狀陣列 模板 include include includeusing namespace std const int maxn 1e6 10 int c maxn int lowbits int x void update int x,int y,int n int getsum int x r...