树状数组初步理解
學(xué)習(xí)樹狀數(shù)組已經(jīng)兩周了,之前偷懶一直沒有寫,趕緊補(bǔ)上防止自己忘記(雖然好像已經(jīng)忘得差不多了)。
作為一種經(jīng)常處理區(qū)間問題的數(shù)據(jù)結(jié)構(gòu),它和線段樹、分塊一樣,核心就是將區(qū)間分成許多個小區(qū)間然后通過對大區(qū)間的調(diào)用來提升效率。因此,我們主要需要了解的就是這種分塊方式。
不同于線段樹直接將區(qū)間不斷的進(jìn)行二分,我們將區(qū)間二分的同時將父節(jié)點(diǎn)直接放在右區(qū)間上,從而形成了一個占用空間很小的樹。
我們仔細(xì)觀察這張圖:根據(jù)我們剛才的思想,每個右節(jié)點(diǎn)都儲存著左右兩個子區(qū)間的和,即右節(jié)點(diǎn)本身就是自己的父節(jié)點(diǎn),而右節(jié)點(diǎn)的父節(jié)點(diǎn)就是父節(jié)點(diǎn)的父節(jié)點(diǎn)。想象一下,原本是一個立體的二叉樹,我們現(xiàn)在將它向右壓,硬生生將一個二維的樹壓成了一個數(shù)組,就得到——樹狀數(shù)組!!!
雖然占用空間大大減小,但是對結(jié)點(diǎn)的訪問現(xiàn)在變成了一個問題,我們應(yīng)該怎么訪問父親結(jié)點(diǎn)和子節(jié)點(diǎn)呢?
仔細(xì)觀察,我們發(fā)現(xiàn)結(jié)點(diǎn)n是x個結(jié)點(diǎn)的祖先,這個x就是n的二進(jìn)制的不為零的最小位(不要問我為什么能看出來,我也看不出來啊,也不知道哪位神仙想出來的),我們用一個函數(shù)lowbit(n)來表示這個神奇的數(shù)字。因?yàn)楦腹?jié)點(diǎn)是右偏的(杜撰的專業(yè)術(shù)語233),所以右邊lowbit(n)個元素和這個區(qū)間是并列的,并且右邊lowbit(n)長的區(qū)間最右邊的元素是左右兩個區(qū)間的祖先,它的位置我們很容易得到是n+lowbit(n)——這就是子節(jié)點(diǎn)到父節(jié)點(diǎn)的訪問方式。
結(jié)點(diǎn)n是左邊lowbit(n)個元素的祖先,所以n-lowbit(n)就是另外一個與n所在區(qū)間緊鄰而且沒有重疊的區(qū)間,所以我們想要遍歷n以前所有的元素時只需要不斷的進(jìn)行n-lowbit(n)直到n==0表示已經(jīng)跳出區(qū)間,通過這種方法我們能夠方便的得到前綴和。
通過上面的總結(jié),我們得到了對樹狀數(shù)組進(jìn)行訪問的初步方法。
lowbit()函數(shù)的實(shí)現(xiàn)方式一般開來說時lowbit(x)=x&(-x);至于為什么這就涉及到玄學(xué)的二進(jìn)制知識,有興趣的話自己去了解一下吧。
根據(jù)以上分析,我們可以得到初步的對樹狀數(shù)組建立以及維護(hù)的方法:
假如我們想要得到的是區(qū)間求和:
如何進(jìn)行查詢呢?
int sum(int x) //x的前綴和 {int ans=0;for(;x;x-=lowbit(x)){ans+=c[x];}return ans; }如果想要得到中間一段區(qū)間的和,不難想到query(x,y)=sum(y)-sum(x-1);
這樣,我們就初步實(shí)現(xiàn)了樹狀數(shù)組的維護(hù)和查詢。
下附一道練手題:
POJ - 2352 Stars
大概意思就是說求一個二維數(shù)組中處于左下角的元素的個數(shù)。乍一看好像是一個二維的樹狀數(shù)組,但是分析一下數(shù)據(jù)范圍就會發(fā)現(xiàn)不太現(xiàn)實(shí)。而且題目中說給出的數(shù)據(jù)是有順序的,因此我們可以分析一下,不難發(fā)現(xiàn)(就當(dāng)作不難吧)后面輸入的數(shù)據(jù)對前面輸入的數(shù)據(jù)是沒有影響的,而且對于每一顆星星來講,處于它下方右邊的并沒有什么意義,因此我們不妨將這個圖形一維化,每個星星的亮度就是所有處于它左邊(和它所在位置但不包括它)星星的個數(shù)。問題的思路應(yīng)該很清晰,下附ac代碼:
至于樹狀數(shù)組的區(qū)間修改區(qū)間查詢,將會在樹狀數(shù)組的進(jìn)一步理解中說明。
總結(jié)
- 上一篇: 2022年卡塔尔世界杯32强揭晓,夺冠热
- 下一篇: 优先队列小结