1. 程式人生 > >二維RMQ問題

二維RMQ問題

前置知識

問題引入

  • 題目地址IN

對於一個n×mn\times m的矩陣,每個格子有一個值,有QQ個詢問,每次詢問你一個子矩陣中的最大值。

1n,m500,Q1061\leq n,m\leq 500,Q\leq 10^6

  • 暴力

每次花子矩陣大小的複雜度去查詢。 複雜度最壞O(Q×n×m)O(Q\times n\times m)

  • 改進

我們用nn棵線段樹或者樹狀陣列來維護每行區間最大值,複雜度最壞O(nmlogm+Qnlogm)O(nmlogm+Qnlogm),還是沒法過,有沒有更快的方法呢?

  • 分析

既然是靜態的詢問,沒有修改操作,那麼很容易聯想到RMQRMQ中的STST表。

那麼暴力的STST表就是和資料結構的做法類似,預處理nnSTST表,每次在多個STST表中查詢最大值,複雜度最壞為O(nmlogm+Qn)O(nmlogm+Qn),雖然能過更多的資料,但是還是不夠。

  • 二維STST

既然查詢物件是個二維矩陣,那麼我們能不能維護一個二維的STST表呢?答案顯然是肯定的。

預處理

所以我們令st[i][j][k][l]st[i][j][k][l],為新的STST表,表示以(i,j)(i,j)

為左上角,右下角為(i+2k1,j+2l1)(i+2^k-1,j+2^l-1),那麼我們可以看出預處理的複雜度會是O(nmlognlogm)O(nmlognlogm),所以對於詢問數交少的還是用資料結構O(nmlogm)O(nmlogm)預處理查詢比較好。

然後我們來看,對於每個st[i][j][k][l]st[i][j][k][l],可以由哪些狀態更新。

我們來看這個狀態表示的矩陣,如下圖: eg

假設這裡左上角的點AA(i,j)(i,j),右下角點DD(i+2k

1,j+2l1)(i+2^k-1,j+2^l-1),那麼我們可以把它分成兩部分,如下圖:

eg

那麼我們將點EE看作(i,j+2l1)(i,j+2^{l-1}),其實原來的大矩陣就可以由分成的這兩個小矩陣更新得到,轉移如下: st[i][j][k][l]=max{st[i][j][k][l1],st[i][j+2l1][l1]}st[i][j][k][l]=\max\{st[i][j][k][l-1],st[i][j+2^{l-1}][l-1]\}

其中,max\max裡面第一個為上半部分矩陣,後面一個為下半部分矩陣。

如果l=0l=0的話,就將其豎起剖成兩部分即可,是同理的。轉移如下:

st[i][j][k][l]=max{st[i][j][k1][l],st[i+2k1][j][k1][l]}st[i][j][k][l]=\max\{st[i][j][k-1][l],st[i+2^{k-1}][j][k-1][l]\}

所以最後按照k,lk,l從小到大更新即可。

查詢

對於一個子矩陣,我們假設它的左上角座標為(x1,y1)(x_1,y_1),右下角為(x2,y2)(x_2,y_2),那麼可以通過預處理的二維STST表,將其分成四部分查詢,如下圖:

eg

其中四個部分為圖中W1(A,E,F,G),W2(H,B,J,I),W3(M,L,K,C),W4(O,P,D,N)W_1(A,E,F,G),W_2(H,B,J,I),W_3(M,L,K,C),W_4(O,P,D,N),查詢區間是可以重合的。

其實就對應瞭如下四個預處理的狀態,我們令p=log2(x2x1+1),q=log2(y2y1+1)p=log_2(x_2-x_1+1),q=log_2(y_2-y_1+1)

max{st[x1][y1][p][q]W1st[x22p+1][y1][p][q]W2st[x1][y22q+1][p][q]W3st[x22p+1][y22q+1]W4}ans\max\begin{cases}st[x_1][y_1][p][q]\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \rightarrow\ \ W_1\\ st[x_2-2^p+1][y_1][p][q] \ \ \ \ \ \ \ \ \ \ \rightarrow\ \ W_2\\ st[x_1][y_2-2^q+1][p][q]\ \ \ \ \ \ \ \ \ \ \rightarrow\ \ W_3\\ st[x_2-2^p+1][y_2-2^q+1] \ \ \ \rightarrow\ \ W_4\end{cases}\bigg\}\rightarrow ans

答案就為上面四個矩陣的max\max,所以預處理對數,每次O(1)O(1)回答即可。

那麼總的複雜度為O(nmlognlogm+Q)O(nmlognlogm+Q),是可以過的了。

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Log=12;
const int N=610;
const int inf=1e9;
int n,m,Q;
int maxv[Log][Log][N][N];
int pre[N],val[N][N]; 
void init(){
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)maxv[0][0][i][j]=val[i][j];
	pre[2]=pre[3]=1;
	for(int i=4,up=max(n,m);i<=up;i++)pre[i]=pre[i>>1]+1;
	int up1=pre[n]+1,up2=pre[m]+1;
	for(int l1=0;l1<=up1;l1++){
		for(int l2=0;l2<=up2;l2++){
			if(!l1&&!l2) continue;
			for(int i=1;(i+(1<<l1)-1)<=n;i++){
				fo