01 故事起源有N個數(shù)排列成一排,如何快速進行區(qū)間修改與求和呢?
?
?02
分析首先最容易想到的方法就是先求出前綴和sum[i],然后區(qū)間[a,b]的和就可以直接通過sum[b]-sum[a-1]得到。
?但如果要對數(shù)組進行修改,就會有一些問題。比如對a[3]加1,則下面對應(yīng)的sum[3],sum[4],sum[5]都需要進行修改,這個效率就很低了。
?原因在于sum[i]是前面區(qū)間[1,i]中所有元素的和,所以修改任何一個元素,則后面的sum[i]都得重新計算。
那能不能找到一種間斷式的前綴和呢,只需要統(tǒng)計前面區(qū)間中的部分元素。這樣在修改某個a[i]的時候就不會影響后面的所有sum[i]。
?其實就是要找到這樣的一種映射關(guān)系,既能統(tǒng)計出前綴和,還可以提高修改的效率。sum[i]以前是統(tǒng)計區(qū)間[1,i]所有的i個元素,而現(xiàn)在是統(tǒng)計區(qū)間[1,i]中的k個元素。
?樹狀數(shù)組其實就是這樣的一種映射。
03
定義樹狀數(shù)組是按下面這種對應(yīng)關(guān)系來計算前面若干元素的和,但直接看可能還看不出來規(guī)律。
?先把元素的下標(biāo)1、2、3...轉(zhuǎn)成二進制。
?再把每個二進制數(shù),從右向左,截取到第一個1的位置。截取的二進制數(shù)也會對應(yīng)一個十進制數(shù)。
?比如12對應(yīng)的二進制數(shù)為1100,截取的二進制數(shù)為100,而100轉(zhuǎn)為十進制為4。所以我們可以定義這樣一種運算,lowbit(12)=4。
?
?那這個lowbit要如何快速計算呢?
計算機原理中,首先我們知道有原碼,反碼,補碼。最高位為符號位,0為正數(shù),1為負數(shù)。正數(shù)的三碼相同,負數(shù)的反碼是符號位不變,其余位取反,而補碼則是反碼加1。在計算機中負數(shù)是以補碼的方式存儲的。
然后再看下面的12和-12,補碼進行位與操作時,就正好是lowbit運算。
代碼實現(xiàn):intlowbit(intx){ returnx&-x; } 把上面的對應(yīng)位置的lowbit都計算出來再觀察,可以發(fā)現(xiàn)lowbit的數(shù)值正好就是sum[i]統(tǒng)計的元素個數(shù)。
?總結(jié)一般的規(guī)律如下: sum[i]等于區(qū)間[i-lowbit(i)+1,i]中所有元素的和。也就是從位置i開始,往前數(shù)lowbit(i)個元素,加起來就是sum[i]。
?
?04
規(guī)律lowbit(i)對應(yīng)的數(shù)一定是1,2,4...,因為截取的二進制為1000...。根據(jù)lowbit(i)可以先對sum[i]進行分層。
?而sum[i]元素也有一種包含關(guān)系,再把包含關(guān)系提上來。
?sum[i]就是前面連續(xù)的lowbit(i)個元素的和,直接展開更清晰。紅色矩形就是下面覆蓋的藍色小方塊的和。
?紅色是sum數(shù)組,藍色是a數(shù)組,再觀察下標(biāo)之間的關(guān)系。
?
?05
單點修改例如修改a[2],因為sum[2],sum[4]都包含了a[2],所以對應(yīng)都要修改。
?如果修改a[3],因為sum[3],sum[4]都包含了a[3],所以對應(yīng)都要修改。
?觀察發(fā)現(xiàn),修改一個元素a[i]時,sum[i]是一層一層的向上進行修改,上一層的下標(biāo)正好是當(dāng)前層的下標(biāo)i加上lowbit(i)。
代碼實現(xiàn):voidadd(intindex,intx){ while(index<=?n)?{ ????????sum[index]?+=?x; ????????index?+=?lowbit(index); ????} } ? ?06 區(qū)間查詢例如查詢區(qū)間[1,5],需要統(tǒng)計sum[5],sum[4]。
?如果查詢區(qū)間[1,3],需要統(tǒng)計sum[3],sum[2]。
?觀察發(fā)現(xiàn),查詢區(qū)間[1,i]的前綴和時,是一段一段往前查詢的,下一段的下標(biāo)正好是當(dāng)前段的下標(biāo)i減去lowbit(i)。
代碼實現(xiàn):intquery(intindex){ intret=0; while(index>0){ ret+=sum[index]; index-=lowbit(index); } returnret; } 如此,就可以輕松搞定單點修改及區(qū)間查詢了,但最開始的問題是區(qū)間修改,這個又該如何實現(xiàn)呢? 07 區(qū)間修改首先得引入一個差分數(shù)組d[i],d[i]=a[i]-a[i-1]。
?對數(shù)組d[i]計算前綴和,又可以還原為原數(shù)組元素a[i]。
?通過公式替換,原數(shù)組的前綴和sum[i]也可以通過d[i]來得到。
?展開來看就是這樣。
?通過觀察,可以對上面公式作如下變形。其中最關(guān)鍵的是sigma(d[j])和sigma(d[j]*j)。
?如果維護d[i]和d[i]*i兩個數(shù)組的前綴和,就可以快速得到sum[k]。
?當(dāng)對區(qū)間[3,5]增加2時,因為d[i]是差分數(shù)組,所以只需要對d[3]增加2,對d[6]減去2即可。同理e[i]數(shù)組,只需要e[3]增加2*3,對e[6]減去2*6。
?一般規(guī)律如下:
代碼實現(xiàn):#defineLLlonglong //單個修改 voidadd(LL*sum,LLindex,LLx){ while(index<=?n)?{ ????????sum[index]?+=?x; ????????index?+=?lowbit(index); ????} } //區(qū)間修改 voidrange_add(LLleft,LLright,LLx){ right++; add(sum1,left,x); add(sum1,right,-x); add(sum2,left,x*left); add(sum2,right,-x*right); } 08 區(qū)間查詢代碼實現(xiàn):
//單個查詢 LLquery(constLL*sum,LLindex){ LLret=0; while(index>0){ ret+=sum[index]; index-=lowbit(index); } returnret; } //區(qū)間查詢 LLrange_query(LLleft,LLright){ left--; LLsumA=(left+1)*query(sum1,left)-query(sum2,left); LLsumB=(right+1)*query(sum1,right)-query(sum2,right); returnsumB-sumA; } 09 總結(jié)樹狀數(shù)組主要應(yīng)用于區(qū)間操作,相比起線段樹來說,代碼實現(xiàn)簡單太多了,而且效率也很高,非常值得研究掌握。 審核編輯 :李倩
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。
舉報投訴
-
代碼
+關(guān)注
關(guān)注
30文章
4973瀏覽量
74155 -
數(shù)組
+關(guān)注
關(guān)注
1文章
420瀏覽量
27407
原文標(biāo)題:原來樹狀數(shù)組可以這么簡單?
文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數(shù)據(jù)結(jié)構(gòu)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
熱點推薦
RT-Thread Vector軟件包:嵌入式開發(fā)的動態(tài)數(shù)組容器 | 技術(shù)集結(jié)
RT-Thread Vector軟件包:嵌入式開發(fā)的動態(tài)數(shù)組容器 | 技術(shù)集結(jié)
利用arm-DSP庫進行FFT的計算
電力系統(tǒng)中往往摻雜諧波,而FFT可以將諧波檢測出來,具有較大的實用價值。今天主要講一下在STM32中如何利用dsp庫進行快速傅里葉計算,從而得出信號的頻譜幅值以及相位。
一、Matlab簡單搭建1.
發(fā)表于 01-21 08:19
linux-arm開發(fā)環(huán)境的簡單配置
linux-arm開發(fā)環(huán)境簡單配置
關(guān)于linux-arm開發(fā)環(huán)境簡單配置是ARM學(xué)習(xí)的第一步,很多初學(xué)者會在這問題上糾結(jié)很久都不能配置好開發(fā)環(huán)境。推薦大家看一下韋東山視頻,講得很詳細,代碼基本上
發(fā)表于 01-13 07:56
數(shù)組的初體驗
程序中也需要容器,只不過該容器有點特殊,它在程序中是一塊連續(xù)的,大小固定并且里面的數(shù)據(jù)類型一致的內(nèi)存空間,它還有個好聽的名字叫數(shù)組。可以將數(shù)組理解為大小固定,所放物品為同類的一個購物袋,在該購
物
發(fā)表于 11-25 08:06
二維數(shù)組介紹
大家不要認為二維數(shù)組在內(nèi)存中就是按行、列這樣二維存儲的,實際上,不管二維、三維數(shù)組… 都是編譯器的語法糖。
存儲上和一維數(shù)組沒有本質(zhì)區(qū)別,舉個例子:
int array[3][3
發(fā)表于 11-25 07:42
請問keil+Env怎么把很大的數(shù)組定義到SDRAM中?
keil+Env怎么把很大的數(shù)組定義到SDRAM中?
RTT自帶的SDRAM程序運行正常,能夠申請里面的空間。
但是沒有辦法把很大的數(shù)組——ltdc_lcd_framebuf[1280][800]
定義到SDRAM中,一運行就出錯,請問各位大佬怎么解決啊?
發(fā)表于 10-11 16:10
SGTools--動畫控件--屏幕實現(xiàn)動畫顯示 就是這么簡單
詳細步驟可以觀看視頻,
實現(xiàn)動畫很簡單,提前準(zhǔn)備好gif文件和一個張背景圖
使用SGTools工具,就可以制作動畫界面啦
視頻中屏幕型號是7寸 HMT070ATA-9C
發(fā)表于 09-16 10:29
大數(shù)組程序無法運行怎么解決?
主控是103,程序中定義一個const類型 128k只讀數(shù)組,放在flash上,程序無法運行,堆棧都初始化不了,在keil編譯下正常,在rtthread studio下編譯無法運行,求教
是內(nèi)存管理的問題嗎
發(fā)表于 09-15 06:21
CUBEIDE調(diào)試過程中,如何將數(shù)組仲的數(shù)據(jù)拷貝到電腦?
請問,有什么辦法可以在CUBEIDE 調(diào)試過程中,將數(shù)組的數(shù)據(jù)拷貝到電腦上去?
發(fā)表于 09-09 07:20
諧波怎么處理最簡單的方法
諧波問題是電力系統(tǒng)中常見的電能質(zhì)量問題,它不僅影響設(shè)備正常運行,還可能造成能源浪費和設(shè)備損壞。針對諧波處理的最簡單方法,我們可以從以下幾個方面入手: 一、理解諧波產(chǎn)生的原因 諧波主要由非線性負載產(chǎn)生
如何使用閃存來保存 CYBT-343026 中的數(shù)組等數(shù)據(jù)?
您好,我正在嘗試使用 CYBT-343026 構(gòu)建一塊電路板。
我想將數(shù)據(jù)存儲在一個簡單的數(shù)組中。T
即使斷電,數(shù)據(jù)也應(yīng)該保留。我可以使用EEPROM,但由于數(shù)據(jù)非常簡單,所以我想使用
發(fā)表于 06-25 06:33
可以通過并聯(lián)RGB接口連接TFT屏幕嗎?
我正在嘗試通過并聯(lián) RGB 接口連接 TFT 屏幕,看起來很簡單,對吧?
?
24 位 RGB 的 LPC4088 僅使用 3 個 4 字節(jié)的 DMA RAM(浪費 1 個字節(jié))
我找不到
發(fā)表于 04-02 06:15
看完這篇,SPI其實也很簡單嘛(可下載)
首先我們來簡單介紹一下SPI,SPI是串行外設(shè)接口(SerialPeripheralInterface)簡單來講就是它一種高速的,全雙工,同步的通信總線被各種總線搞的暈頭轉(zhuǎn)向的人來說就會問了
發(fā)表于 03-26 14:29
?3次下載
樹狀數(shù)組可以很簡單
評論