樹狀數組(單點更新-區間查詢,區間更新-單點查詢,區間更新-區間查詢,二維樹狀數組)

樹狀數組(單點更新-區間查詢,區間更新-單點查詢,區間更新-區間查詢)

樹狀數組和線段樹的比較:

1.都用來求區間問題,優化時間複雜度。它和線段樹有着相似的功能,都能求,單點更新-區間查詢,區間更新-單點查詢,區間更新-區間查詢 這些問題。

2.二者有着相似的時間複雜度。log()級的查詢與修改。

3.樹狀數組的功能,用線段樹完全能夠實現,但是由線段樹能實現的功能樹狀數組不一定能夠實現。 線段樹>樹狀數組。

4.既然功能不如線段樹多,那爲什麼還要學樹狀數組呢?因爲樹狀數組變成簡單啊。代碼少,寫起來快啊~~。

樹狀數組的原理:

樹狀數組,顧名思義就是樹狀的數組。如圖,就是一個變形的完全二叉樹。
在這裏插入圖片描述
我們給每個塊塊都標上數值如下:
在這裏插入圖片描述
A[]是原數組,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]
這樣我們更新A數組的時候 只需要更新與A相關的C數組就好,比如你要更新 A[1] 只需要更新與A[1] X相關的 C[1] C[2] C[4] C[8] 就好。
那麼問題來了,我們這個C數組是怎麼分的呢??轉換給2進制就很明瞭了
C[1] = C[0001] = A[1];

    C[2] = C[0010] = A[1]+A[2];

    C[3] = C[0011] = A[3];

    C[4] = C[0100] = A[1]+A[2]+A[3]+A[4];

    C[5] = C[0101] = A[5];

    C[6] = C[0110] = A[5]+A[6];

    C[7] = C[0111] = A[7];

    C[8] = C[1000] = A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

C數組管轄的A的個數 = 2^k(個) k等於啥呢。 k就是這個二進制的從後往前連續0的個數,比如C[8] = C[1000] 後面連續三個0 所以k 就等於3 再比如C[6] =C[0110] 從後往前連續的0只有1個就斷了 所以k=1.

那麼 問題又來了,雖然我們明白了原理,知道了C 數組 怎麼來的,我們怎麼實現呢??
這裏引入一個 lowbit(x) 函數,函數的功能就是取 x的最低位的1.代碼很短。但是樹狀數組全靠它。

int lowbit(int x){
	return x&(-x);
}

取最後位的1 啥用呢?你想啊,我們每次更新的時候,要找相關的C 我們只要每次加一下lowbit不就好了嘛~
比如更新A[1] ,我們要更新的C分別得C[1] C[2] C[4] C[8]
1+lowbit(1) . 1的最低位的1還是1 所以1+lowbit(1) =2
2+lowbit(2) . 2=10 最低位的1 代表2 所以 2+lowbit(2) = 2+2 = 4;
4+lowbit(4) . 4=100 最低位的1 代表4 所以 4+lowbit(4)= 4+4= 8
同樣,查詢的時候我們只要往前查詢就行了。每次減 lowbit()

單點更新,區間查詢。

**更新操作:**正如我們前面所說的,更新的時候,只需要更新與它相關聯的C數組就好,所以我們往上找,每次加lowbit()

void update(int x,int val)
{
	for(int i=x;i<=n;i+=lobwit(i)){
		c[i]+=val;
	}
}

查詢操作:,查詢應該查詢往下找,每次減lowbit()
需要注意的是!! 我們 這樣得到的結果是 A[1]+A[2]+…+A[N];

int query(int x){
	int res=1;
	for(int i=x;i>0;i-=lowbit(i)){
		res+=c[i];
	}
	return res;
}

我們要查詢 L-R 這段區間的結果怎麼查呢?
這個就很簡單了啊,就是利用前綴和的思想嘛。

int query_range(int l,int r){
	return query(r)-query(l-1);
}

這樣我們在主函數中,在輸入數組A的時候 順便更新着數組C 然後直接查詢就好了。
例題:

HDU-1166 博客:https://blog.csdn.net/weixin_43179892/article/details/89493087
POJ-2352 博客:https://blog.csdn.net/weixin_43179892/article/details/89493357

區間更新,點查詢

區間更新,點查詢。
這時候我們維護的C數組,不再是和了 而是差。
給定一個數組A = 1 4 7 3 8
求相鄰兩個數組的差
B[1]=1-0=1;
B[2]=4-1=3;
B[3]=7-4=3
B[4]=3-7=-4
B[5]=8-3=5;
這是
A[2] = B[1]+B[2]
A[3] = B[1]+B[2] +B[3]

我們這時候對B,進行樹狀數組的操作。
查詢操作:求第k個位置的數,只要查詢 前k個位置的和就好了。
更新操作:如果要 L-R 的所有數都+ 2 我們只要更新 B[L]+=2 B[R+1]-=2就好了
舉個例子,如果我們要 3-4的數每個都+2 我們讓B[2]+=2=5 B[5]-=2;
此時你再求A[3]的時候 就是 b[1]+b[2]+b[3]=9 就是A[3]+=2 A[4]也類似
求A[5] = B[1]+B[2] +B[3] +B[4] +B[5] =8 還是原來的8 所以不會影響到後面的數。

本質上區間更新-點查詢 和 點更新,區間查詢是一樣的,只是二者維護的東西不一樣,一個是本身的值,而一個是差。 就連代碼也基本相似,只是在主函數中,更新的時候稍有不同。
區間更新的代碼。update 函數就跟點更新的一樣。

void update_range(int l,int r,int val){
	update(l,val);
	update(r+1-val);
}

看一道例題基本上就能明白這個了。
例題:
HDU-1556 博客:https://blog.csdn.net/weixin_43179892/article/details/89493508
HDU-1754 博客:https://blog.csdn.net/weixin_43179892/article/details/89493821

區間更新,區間查詢

區間更新,區間查詢就是區間更新,點查詢的延續
我們還是考慮差分的思想,我們設數組B還是存的兩點之間的差。
單點查詢的時候,我們只要一次query()就能該單點的值。
1-n的區間和怎麼求呢?
A[1]=B[1];
A[2]=B[1]+B[2];
A[3]=B[1]+B[2]+B[3];
A[4]=B[1]+B[2]+B[3]+B[4];

A[n]=B[1]+B[2]+B[3]+B[4]+…B[n];

A[1]+A[2]+A[3]+…A[n] =
B[1]n +B[2](n-1)+B[3]*(n-1)+…B[n]*1=
(B[1]+B[2]+B[3]+…B[n])*n - (B[1]*0+B[2]*1+B[3]2+…B[n](n-1));
這就是簡單的公式化簡,應該不難看懂吧?
爲什麼這麼化呢???我們來看這個式子

(B[1]+B[2]+B[3]+…B[n])*n - (B[1]*0+B[2]*1+B[3]2+…B[n](n-1));

前面這一部分不就是區間更新,單點查詢中的那個單點查詢嘛~
後面那一部分我們維護一個別的數組sum2 存的是 B[i]*(i-1),這樣我們只要把兩個數組查詢結果一減就是我們要求的 1–n的和。 我們要查L-R的區間,還是用前綴和的思想。q®-q(l-1) (這裏的q是查詢函數)。

幹看字可能看的很蒙,下面給出一個函數模板:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int sum1[maxn];//維護c1 + c2 + ....cn
int sum2[maxn];//維護 1*c1 + 2*c2 +...n*cn;
int a[maxn];
int lowbit(int x){
	return x&(-x);
}
void update(int x,int val){
	for(int i=x;i<=n;i+=lowbits(i)){
		sum1[i]+=val;
		sum2[i]+=(val)*(x-1);
	}
}
void update_range(int l,int r,int val){
	update(l,val);
	update(r+1,-val);
}
int query(int x){
	int res=0;
	for(int i=x;i>0;i-=lowbit(i)){
		res+= x*sum1[i]-sum2[i];
	}
	return res;
}
int query_range(int l,int r){
	return query(r)-query(l-1);
}

二維樹狀數組

二維樹狀數組解決什麼問題呢?顧名思義就是二維的問題啦。
比如這麼一個問題:
給你一個二維矩陣。給你兩個操作:
A操作時 把x1 y1 的值加 上 val.
B操作時查詢 x1 y1 x2 y2(分別表示矩形的左下角和 右上角) 這個矩形內的q權值和。

笨辦法肯定是A操作的時候直接加val,B查詢操作是就掃一遍需要查詢的矩形,求一下和。但是這麼傻的方法在 ACM比賽中 它百分百會超時的。

所以我們就引入了我們的二維樹狀數組。
二維樹狀數組其實和一維一模一樣,就是把一維的變成二維的就好了。
*更新單點的時候,你不僅要更新x方向的,還要更新y方向的。那就是兩次for循環的事。但是每一層的複雜度都是log級的,所以整個單點更新 是log()log() 級的

void updata(int x,int y,int num)
{
    int i,j;
    for(i=x; i<=n; i+=lowbit(i))
        for(j=y; j<=n; j+=lowbit(j))
            c[i][j]+=num;
}

同樣,查詢也是類似的,我們的query(x,y)函數求的是從(1,1)到(x,y)這個區間的和。這個也是x查 y查

int sum(int x,int y)//求(0,0)到 (x,y)之間的所有元素和
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
		for(int j=y;j>0;j-=lowbit(j))
			res+=c[i][j];
	return res;
}

但是我們要求的是 x1,y1,x2,y2,這個矩形內的和,這個地方就是二維前綴和的思想。
在這裏插入圖片描述
所以我們這麼算就好了 (縫縫補補,湊出我們要的區間)
query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);

int query_range(int x1,int y1,int x2,int y2)
{
    return query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1);
}

例題
POJ-1195 博客:https://blog.csdn.net/weixin_43179892/article/details/89494106

還會陸陸續續的補充一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章