樹狀陣列經典講解
由於原作者已經清空博文,可能進連結並不會有什麼,但謹此以表示對原作的尊重。
樹狀陣列
第01講 什麼是樹狀陣列?
樹狀陣列用來求區間元素和,求一次區間元素和的時間效率為O(logn)。
有些同學會覺得很奇怪。用一個數組S[i]儲存序列A[]的前i個元素和,那麼求區間i,j的元素和不就為S[j]-S[i-1],那麼時間效率為O(1),豈不是更快?
但是,如果題目的A[]會改變呢?例如:
我們來定義下列問題:我們有n個盒子。可能的操作為
1.向盒子k新增石塊
2.查詢從盒子i到盒子j總的石塊數
自然的解法帶有對操作1為O(1)而對操作2為O(n)的時間複雜度。但是用樹狀陣列,對操作1和2的時間複雜度都為O(logn)。
第02講 圖解樹狀陣列C[]
現在來說明下樹狀陣列是什麼東西?假設序列為A[1]~A[8]
網路上面都有這個圖,但是我將這個圖做了2點改進。
(1)圖中有一棵滿二叉樹,滿二叉樹的每一個結點對應A[]中的一個元素。
(2)C[i]為A[i]對應的那一列的最高的節點。
現在告訴你:序列C[]就是樹狀陣列。
那麼C[]如何求得?
C[1]=A[1];
C[2]=A[1]+A[2];
C[3]=A[3];
C[4]=A[1]+A[2]+A[3]+A[4];
C[5]=A[5];
C[6]=A[5]+A[6];
C[7]=A[7];
C[8]= A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
以上只是枚舉了所有的情況,那麼推廣到一般情況,得到一個C[i]的抽象定義:
因為A[]中的每個元素對應滿二叉樹的每個葉子,所以我們乾脆把A[]中的每個元素當成葉子,那麼:C[i]=C[i]的所有葉子的和。
現在不得不引出關於二進位制的一個規律:
先仔細看下圖:
將十進位制化成二進位制,然後觀察這些二進位制數最右邊1的位置:
1 --> 00000001
2 --> 00000010
3 --> 00000011
4 --> 00000100
5 --> 00000101
6 --> 00000110
7 --> 00000111
8 --> 00001000
1的位置其實從我畫的滿二叉樹中就可以看出來。但是這與C[]有什麼關係呢?
接下來的這部分內容很重要:
在滿二叉樹中,
以1結尾的那些結點(C[1],C[3],C[5],C[7]),其葉子數有1個,所以這些結點C[i]代表區間範圍為1的元素和;
以10結尾的那些結點(C[2],C[6]),其葉子數為2個,所以這些結點C[i]代表區間範圍為2的元素和;
以100結尾的那些結點(C[4]),其葉子數為4個,所以這些結點C[i]代表區間範圍為4的元素和;
以1000結尾的那些結點(C[8]),其葉子數為8個,所以這些結點C[i]代表區間範圍為8的元素和。
擴充套件到一般情況:
i的二進位制中的從右往左數有連續的x個“0”,那麼擁有2^x個葉子,為序列A[]中的第i-2^x+1到第i個元素的和。
終於,我們得到了一個C[i]的具體定義:
C[i]=A[i-2^x+1]+…+A[i],其中x為i的二進位制中的從右往左數有連續“0”的個數。
第03講 利用樹狀陣列求前i個元素的和S[i]
理解了C[i]後,前i個元素的和S[i]就很容易實現。
從C[i]的定義出發:
C[i]=A[i-2^x+1]+…+A[i],其中x為i的二進位制中的從右往左數有連續“0”的個數。
我們可以知道:C[i]是肯定包括A[i]的,那麼:
S[i]=C[i]+C[i-2^x]+…
也許上面這個公式太抽象了,因為有省略號,我們拿一個具體的例項來看:
S[7]=C[7]+C[6]+C[4]
因為C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]
(1)i=7,求得x=0,那麼我們求得了A[7];
(2)i=i-2^x=6,求得x=1,那麼求得了A[6]+A[5];
(3)i=i-2^x=4,求得x=2,那麼求得了A[4]+A[3]+A[2]+A[1]。
講到這裡其實有點難度,因為S[i]的求法,如果要講清楚,那麼得寫太多的東西了。所以不理解的同學,再反覆多看幾遍。
從(1)(2)(3)這3步可以知道,求S[i]就是一個累加的過程,如果將2^x求出來了,那麼這個過程用C++實現就沒什麼難度。
現在直接告訴你結論:2^x=i&(-i)
證明:設A’為A的二進位制反碼,i的二進位制表示成A1B,其中A不管,B為全0序列。那麼-i=A’0B’+1。由於B為全0序列,那麼B’就是全1序列,所以-i=A’1B,所以:
i&(-i)= A1B& A’1B=1B,即2^x的值。
所以根據(1)(2)(3)的過程我們可以寫出如下的函式:
int Sum(int i) //返回前i個元素和
{
int s=0;
while(i>0)
{
s+=C[i];
i-=i&(-i);
}
return s;
}
第04講 更新C[]
正如第01講提到的小石塊問題,如果陣列A[i]被更新了怎麼辦?那麼如何改動C[]?
如果改動C[]也需要O(n)的時間複雜度,那麼樹狀陣列就沒有任何優勢。所以樹狀陣列在改動C[]上面的時間效率為O(logn),為什麼呢?
因為改動A[i]只需要改動部分的C[]。這一點從第02講的圖中就可以看出來:
如上圖:
假如A[3]=3,接著A[3]+=1,那麼哪些C[]需要改變呢?
答案從圖中就可以得出:C[3],C[4],C[8]。因為這些值和A[3]是有聯絡的,他們用樹的關係描述就是:C[3],C[4],C[8]是A[3]的祖先。
那麼怎麼知道那些C[]需要變化呢?
我們來看“A”這個結點。這個“A”結點非常的重要,因為他體現了一個關係:A的葉子數為C[3]的2倍。因為“A”的左子樹和右子樹的葉子數是相同的。 因為2^x代表的就是葉子數,所以C[3]的父親是A,A的父親是C[i+2^0],即C[3]改變,那麼C[3+2^0]也改變。
我們再來看看“B”這個結點。B結點的葉子數為2倍的C[6]的葉子數。所以B和C[6+2^1]在同一列,所以C[6]改變,C[6+2^1]也改變。
推廣到一般情況就是:
如果A[i]發生改變,那麼C[i]發生改變,C[i]的父親C[i+2^x]也發生改變。
這一行的迭代過程,我們可以寫出當A[i]發生改變時,C[]的更新函式為:
void Update(int i,int value) //A[i]的改變值為value
{
while(i<=n)
{
C[i]+=value;
i+=i&(-i);
}
}
第05講 一維樹狀陣列的應用舉例
廢了4講的話,我們終於把一維樹狀陣列的2個不到5行的程式碼給搞定了。現在要正式投入到應用當中。
題意:按照y升序給你n個星星的座標,如果有m個星星的x,y座標均小於等於星星A的座標,那麼星星A的等級為m。
分析:是一道樹狀陣列題。舉例來說,以下是題目的輸入:
5
1 1
5 1
7 1
3 3
5 5
由於y座標是升序的且座標不重複,所以在星星A後面輸入的星星的x,y座標不可能都小於等於星星A。假如當前輸入的星星為(3,3),易得我們只需要去找 樹狀陣列中小於等於3的值就可以了,即GetSum(3)。注意:A[i]表示x座標為i的個數,C[]為A[]的樹狀陣列,那麼GetSum(i)就是 序列中前i個元素的和,即x小於等於i的星星數。
本題還是一點要注意:星星座標的輸入可以是(0,0),所以我們把x座標統一加1,然後用樹狀陣列實現。
第06講 二維樹狀陣列
BIT可用為二維資料結果。假設你有一個帶有點的平面(有非負的座標)。你有三個問題:
1.在(x , y)設定點
2.從(x , y)移除點
3.在矩形(0 , 0), (x , y)計算點數 - 其中(0 , 0)為左下角,(x , y)為右上角,而邊是平行於x軸和y軸。
對於1操作,在(x,y)處設定點,即Update(x,y,1),那麼這個Update要怎麼寫?很簡單,因為x,y座標是離散的,所以我們分別對x,y進行更新即可,函式如下:
void Update(int x,int y,int val)
{
while(x<=n)
{
int y1=y;
while(y1<=n)
{
C[x][y1]+=val;
y1+=y1&(-y1);
}
x+=x&(-x);
}
}
那麼根據Update可以推得:GetSum函式為:
int GetSum(int x,int y)
{
int sum=0;
while(x>0)
{
int y1=y;
while(y1>0)
{
sum+=C[x][y1];
y1-=y1&(-y1);
}
x-=x&(-x);
}
return sum;
}
第07講 二維樹狀陣列的應用舉例
我們先討論POJ2155的一維情況,如下:
有一個n卡片的陣列。每個卡片倒放在桌面上。你有兩個問題:
1. T i j (反轉從索引i到索引j的卡片,包括第i張和第j張卡——面朝下的卡將朝上;面朝上的卡將朝下)
2. Q i (如果第i張卡面朝下回答0否則回答1)
解決:
解決問題(1和2)的方法有時間複雜度O(log n)。在陣列f(長度n + 1)我們儲存每個問題T(i, j)——我們設定f[i]++和f[j + 1]--。對在i和j之間(包括i和j)每個卡k求和f[1] + f[2] + ... + f[k]將遞增1,其他全部和前面的一樣(看圖2.0清楚一些),我們的結果將描述為和(和累積頻率一樣)模2。
圖 2.0
使用BIT來儲存(增加/減少)頻率並讀取累積頻率。
理解了一維的情況,POJ2155就是其二維的版本,易得只需要更(x1,y1),(x1,y2+1),(x2+1,y1),(x2+1,y2+1)四個點的C[]的值就可以了,最後的結果依然是GetSum(x,y)%2
相關推薦
樹狀陣列經典講解
由於原作者已經清空博文,可能進連結並不會有什麼,但謹此以表示對原作的尊重。 樹狀陣列 第01講 什麼是樹狀陣列? 樹狀陣列用來求區間元素和,求一次區間元素和的時間效率為O(logn)。 有些同學會覺得很奇怪。用一個數組S[i]儲存序列A[]的前i個元素和,那麼
樹狀陣列理論闡述及幾道經典例題講解
1、lowbit操作 函式功能:求某一個數的二進位制表示中最低的一位1。舉個例子,x = 12,它的二進位制為1100,那麼lowbit(x)就返回4,因為最後一位1表示4。 演算法實現:先用x&(x-1)消除最後一位1,再用原數x減去消除最後一位1後的數,即得答案
樹狀陣列的區間加以及區間詢問講解
前置技能 基礎樹狀陣列。不會的話自己百度Orz。 要求掌握樹狀陣列的以下操作: 熟悉樹狀陣列原理以及用途。 單點修改,區間查詢。 區間加,單點查詢。 問題模型 樹狀陣列實現區間加和區間查詢。 實現 記 $A_i$為數列上面的第$i$個元素。
樹狀陣列 區間修改 區間查詢 講解
原來的值存在a[]裡面,多建立個數組c1[],注意:c1[i]=a[i]-a[i-1]。 那麼求a[i]的值的時候: a[i]=a[i-1]+c1[i]=a[i-2]+c1[i]+c1[i-1]=…..=c1[1]+c1[2]+…+c1[i]。 我們叫c1[]陣列為差分
樹狀陣列 講解和題目集
"第一分鐘,X說,要有數列,於是便給定了一個正整數數列。第二分鐘,L說,要能修改,於是便有了對一段數中每個數都開平方(下取整)的操作。第三分鐘,k說,要能查詢,於是便有了求一段數的和的操作。第四分鐘,彩虹喵說,要是noip難度,於是便有了資料範圍。第五分鐘,詩人說,要有韻律,於是便有了時間限制和記憶體
樹狀陣列萌新講解+基礎習題【一點一滴】
樹狀陣列基礎篇 樹狀陣列講點 中文名:樹狀陣列 英文名:Binary Indexeds Tree 英譯中:二進位制索引樹 這特麼多清楚 引入: 給你n個數 1. 求區間的的和 2. 改變某個值 然後樸素做法肯定GG,這裡就有了樹狀陣列
Bestcoder7(1004)hdu4988(經典問題:樹狀陣列套treap求解動態逆序對)
Little Pony and Boast Busters Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) Total Submission(
POJ2155:Matrix(二維樹狀陣列,經典)
Description Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row and j-th column. Initially
字首和 線段樹 樹狀陣列講解(超詳細入門)
部落格目錄 Part one、字首和 引入問題:現輸入長度為n的數列co,再輸入q個詢問,每個詢問都給出兩個整數l,r。對於每個詢問都要求給出對於數列co在區間[l,r]上的和(假設下標從0開始)。 1. 最直觀的方法,就是直接暴力求解,每給出一對l
poj2155 Matrix(經典二維樹狀陣列)
吐槽:這題先說[x1,y1]和[x2,y2]是左上角和右下角的兩個點,又說x1<=x2&&y1<=y2,那就是x軸向右,y軸向下為正方向。mdzz啊,座標系這種東西能不能
樹狀陣列入門(簡單的原理講解)
樹狀陣列可以解決什麼樣的問題: 這裡通過一個簡單的題目展開介紹,先輸入一個長度為n的陣列,然後我們有如下兩種操作: 輸入一個數m,輸出陣列中下標1~m的字首和 對某個指定下標的數進行值的修改 多次執行上述兩種操作 尋常方法 對於一個的陣列,如果需要求1~m的字首和我們可以將其從下標1開始對m個數進行求和
2018.10.29 bzoj3718: [PA2014]Parking(樹狀陣列)
傳送門 顯然只用判斷兩個會相交的車會不會卡住就行了。 直接樹狀陣列維護字尾最大值就行了。 程式碼: #include<bits/stdc++.h> using namespace std; const int N=5e4+5; struct Matrix{int x1,x
異或和(權值樹狀陣列)
異或和(權值樹狀陣列) 題目描述 在加里敦中學的小明最近愛上了數學競賽,很多數學競賽的題都是與序列的連續和相關的。所以對於一個序列,求出它們所有的連續和來說,小明覺得十分的簡單。但今天小明遇到了一個序列和的難題,這個題目不僅要求你快速的求出所有的連續和,還要快速的求出這些連續和的異或值。小明很快的就求出了
poj 3321 dfs序 樹狀陣列 前向星
題意概括 有一顆01樹,以結點1為樹根,一開始所有的結點權值都是1,有兩種操作: 1.改變其中一個結點的權值(0變1,1變0) 2.詢問子樹X的節點權值和。 參考部落格 http://www.cnblogs.com/zhouzhendong/p/7265431.htm
淺談樹狀陣列(解析+模板)
也不知道是什麼時候開始,對於曾經學過的演算法都不太用了 遇到區間修改,區間最值就知道用線段樹,什麼樹狀陣列啊,st表啊都忘得差不多了 最近幾次模考被卡翻了,於是又想起這些老朋友 來填個坑 首先我們要明確一點,樹狀陣列只能維護求和,不能維護區間最值 樹狀陣列利用了分治的思想,層數為
hdu4325-Flowers-樹狀陣列+離散化
Flowers Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 3687
【非原創】codeforces 1070C Cloud Computing 【線段樹&樹狀陣列】
題目:戳這裡 學習部落格:戳這裡 題意:有很多個活動,每個活動有持續天數,每個活動會在每天提供C個CPU每個CPU價格為P,問需要工作N天,每天需要K個CPU的最少花費。 解題思路:遍歷每一天,維護當前天K個cpu的最小花費。具體方法是維護兩個線段樹(樹狀陣列也可以),維護每一天可以使用的cpu數和價格
hust1433-樹狀陣列-2
http://acm.hust.edu.cn/JudgeOnline/problem.php?id=1433 題意:給定一個1...n的排列,對i<=k<j. ans[k]為a[i]>a[j]的對數,求ans[1...n-1]。。。 分析:用樹狀陣列,求出
[zoj4046][樹狀陣列求逆序(強化版)]
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4046 題意:有一個含有n個元素的數列p,每個元素均不同且為1~n中的一個,求出將數列變為迴圈遞增序列至少需要左右相鄰的數交換多少次 題目分析:先看簡化版的題目:如果只有1 2 3
樹狀陣列【洛谷P3586】 [POI2015]LOG
P3586 [POI2015]LOG 維護一個長度為n的序列,一開始都是0,支援以下兩種操作:1.U k a 將序列中第k個數修改為a。2.Z c s 在這個序列上,每次選出c個正數,並將它們都減去1,詢問能否進行s次操作。每次詢問獨立,即每次詢問不會對序列進行修改。 離散化按照權值建立樹狀陣列