樹狀數組實現矩陣中矩形區域的修改以及求和

樹狀數組實現矩形區域的修改以及求和

                 By 巖之痕

講講樹狀數組如何實現對一個矩陣的矩形區域的加法和求和。

以下,(x1,y1)爲矩形的左下角座標,(x2,y2)爲矩形的右上角座標(x1<=x2 , y1<=y2)


矩形加法:Add(int x1,int y1,int x2,int y2,int K)表示給指定矩形區域的元素都加上K

矩形求和:Sum(int x1,int y1,int x2,int y2)  即求

思路就是通過差分,化區間修改爲點修改,然後用樹狀數組來解決。

由於要用到樹狀數組,以下所有數組下標都從1開始,並令A[0]=0,方便討論。

先說說一維的情況:

對於一維數組A,要求區間和,首先化區間和爲前綴和:

於是只需要討論如何求前綴和()就行了。

對於區間修改,直接用樹狀數組來維護A的前綴和是O(n)的時間複雜度。

樹狀數組只支持點修改,於是,想一想,怎麼化區間修改爲點修改?

考慮數組a[i]=A[i]-A[i-1](注意邊界條件A[0]=0,所以i=1的時候這個式子也是符合的),

A數組的區間[L,R]都加上K的時候,a數組只有兩個值改變了,

 a[L] 多了K  ,a[R+1] 少了K,這就做到了化區間修改爲點修改。

我們再來看看如何從a[i]來得到A[i]的前綴和。

首先注意到:

前綴和的計算如下:



擴展到二維的情況:

開始講二維的情況:

類似一維情況中化區間和爲前綴和之差的方法,有如下公式:

於是只需要解決這個問題:

思路當然還是差分,首先縱向差分:

,則

於是:


此時,b[i][j]+= t 代表的含義就是b[i][j],b[i][j+1],...,b[i][n]都增加了t.

也就是實現了縱向的化區間修改爲點修改。

要修改一個矩形區域,也就是需要連續修改i在一個區間的b[i][j],於是將b[i][j]再橫向差分,


(2)式代入(1)式:


由上面公式看出,只需要維護下面四個元素的矩陣和,就可以求出


所以,用二維樹狀數組維護上面四個矩陣和就行了。

此時a[i][j]+=t,代表b[i][j],b[i+1][j],...,b[n][j]都增加了t,

也就代表

A[i][j],A[i][j+1],...A[i][n]都增加了t

A[i+1][j],A[i+1][j+1],...A[i+1][n]都增加了t

...

A[n][j],A[n][j+1],...A[n][n]都增加了t

於是矩形區域的加法也就不難表示了,調用4Add(點修改)就行了。

矩形區域的求和也不難表示了,調用4Sum就行了。

void AddSquare(int x1,int y1,int x2,int y2,int K){
	Add(x1,y1,K);Add(x2+1,y2+1,K);
	Add(x1,y2+1,-K);Add(x2+1,y1,-K);
}
long long SumSquare(int x1,int y1,int x2,int y2){
	return Sum(x2,y2)-Sum(x1-1,y2)-Sum(x2,y1-1)+Sum(x1-1,y1-1);
}


時間複雜度:

暴力法做矩形修改的話,時間複雜度是

二維樹狀數組的時間複雜度:

數組元素個數爲1000*1000的時候,Sum操作平均用到24.38個數組元素,

Add操作平均用到25.60個數組元素。

由於本題維護了4個數組,所以每個Sum平均涉及97.52個元素。

所以每個Add平均涉及102.40個元素。

然後每次操作用到了4Add或者4Sum.

所以二維樹狀數組每次操作平均更改400個數組元素的值。

同等情況下,暴力法需要更改的元素個數最少爲1,最多爲10^6.

1000*1000,隨機數據的情況下暴力法平均每次操作更改62500元素,極限數據下更改1000000元素

隨機數據下用時是二維樹狀數組的156倍,極限數據下是2500倍。


1000*1000時,10000個對整個數組的操作時(極限數據),減去輸入輸出的時間之後:

二維樹狀數組的用時:0.013s,暴力法用時:39.517s  約爲3000倍。


1000*1000,隨機進行100000次操作時,減去輸入輸出時間後:

二維樹狀數組用時:0.314s  暴力法用時:47.27s. 約爲150.54倍。


實驗跟計算基本符合。

最後附上代碼:

#define LL long long
int n,m; 
LL A[1027][1027][4];
LL C[4]; 
void Add(int x,int y,LL K){//點修改
	C[0]=K;C[1]=K*y;C[2]=K*x;C[3]=K*x*y;
	int xx=x;
	while(xx <= n){
		int yy=y; 
		while(yy <= n){
			A[xx][yy][0]+=C[0];
			A[xx][yy][1]+=C[1];
			A[xx][yy][2]+=C[2];
			A[xx][yy][3]+=C[3];
			yy+=yy&-yy; 
		}
		xx+=xx&-xx;
	}
}
LL Sum(int x,int y){//1..x,1..y的區域求和
	C[0]=C[1]=C[2]=C[3]=0; 
	int xx=x;
	while(xx > 0){
		int yy=y;
		while(yy > 0){
			C[0]+=A[xx][yy][0];
			C[1]+=A[xx][yy][1];
			C[2]+=A[xx][yy][2];
			C[3]+=A[xx][yy][3];
			yy-=yy&-yy;
		}
		xx-=xx&-xx;
	}
	return (x+1)*(y+1)*C[0]-(x+1)*C[1]-(y+1)*C[2]+C[3];
}
void AddSquare(int x1,int y1,int x2,int y2,int K){//矩形區域的修改
	Add(x1,y1,K);Add(x2+1,y2+1,K);
	Add(x1,y2+1,-K);Add(x2+1,y1,-K);
}
LL SumSquare(int x1,int y1,int x2,int y2){//矩形區域的求和
	return Sum(x2,y2)-Sum(x1-1,y2)-Sum(x2,y1-1)+Sum(x1-1,y1-1);
}


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