1. 程式人生 > >區間最值的問題——————RMQ問題

區間最值的問題——————RMQ問題

RMQ問題

    RMQ (Range Minimum/Maximum Query)問題是指:對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在[i,j]裡的最小(大)值,也就是說,RMQ問題是指求區間最值的問題RMQ主要思想是分治,倍增與動態規劃。

求解RMQ問題分為兩步,預處理階段和求解(查詢)階段

第一步: 先進行預處理,用動態規劃完成

設A[i]為要求最值的數列,F[i,j]用來表示從第i個數開始連續2^j個數的最大值。(DP的狀態)

A={2,4,3,7,6,1,9,5}

F[1,0] 是指從第一個數開始連續 2^0次方個數,即就是2這個數。F(1,1)=max(2,4) ; F[i,0]=A[i] (DP的初值);接下來就該狀態轉移方程了。我們把F[i,j] 平均分成兩段(因為f[i,j]一定是偶數個數字),從 i 到i + 2 ^ (j - 1) - 1為一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1為一段(長度都為2 ^ (j - 1))。用上例說明,當i=1,j=3時就是2,4,3,7 和 6,1,9,5這兩段。F[i,j] 就是這兩段各自最大值中的最大值。

於是我們得到了狀態轉移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。

RMQ演算法不過是從首部先相鄰的元素取最值,然後再擴大在這些最值裡面。

程式碼如下:

void RMQ(int n)    //n是士兵人數
{
	for(int i=1;i<=n;i++)
	{
		minn[i][0]=a[i]; //a[i] 為第i個士兵殺得人數
		maxx[i][0]=a[i];
	}
	for(int j=1;(1<<j)<=n;j++)                
	for(int i=1;i+(1<<j)-1<=n;i++)
	{
		minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);     //minn[][],儲存的是在區間內的最小值
		maxx[i][j]=max(maxx[i][j-1],maxx[i+(1<<(j-1))][j-1]);  //儲存最大值
	}
}

注意:外層是j,內層所i,不能交換

因為狀態轉移方程的含義是:先更新所有長度為F[i,0]即1個元素,然後通過2個1個元素的最值,獲得所有長度為F[i,1]即2個元素的最值,然後再通過2個2個元素的最值,獲得所有長度為F[i,2]即4個元素的最值,以此類推更新所有長度的最值。

而如果是i在外,j在內的話,我們更新的順序就是F[1,0],F[1,1],F[1,2],F[1,3],表示更新從1開始1個元素,2個元素,4個元素,8個元素(A[0],A[1],....A[7])的最值,這裡F[1,3] = max(max(A[0],A[1],A[2],A[3]),max(A[4],A[5],A[6],A[7]))的值,但是我們根本沒有計算max(A[0],A[1],A[2],A[3])和max(A[4],A[5],A[6],A[7]),所以這樣的方法肯定是錯誤的。

為了避免這樣的錯誤,一定要好好理解這個狀態轉移方程所代表的含義。

第二步查詢

假如我們需要查詢的區間為(i,j),那麼我們需要找到覆蓋這個閉區間(左邊界取i,右邊界取j)的最小冪(可以重複,比如查詢5,6,7,8,9,我們可以查詢5678和6789)。

因為這個區間的長度為j - i + 1,所以我們可以取k=log2( j - i + 1),則有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

舉例說明,要求區間[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);

程式碼如下:

int rmp(int l,int r)
{
	int k=0;
	while((1<<(k+1))<=r-l+1) k++;   //求出最小冪 K,其中(r-l+1)是區間長度,左移運算子是2的(k+1)次方,k+1的目的是,求出的k次方為區間長度的一半	
	int k1=min(minn[l][k],minn[r+1-(1<<k)][k]); //區間的最小值 
	int k2=max(maxx[l][k],maxx[r+1-(1<<k)][k]);//區間的最大值 
	return k2-k1;  
}
或者求最小冪改成  
  int k = (int)(log(r-l+1)/log(2)); 

例題  請訪問  這裡