首先發出題目鏈接:
鏈接:https://ac.nowcoder.com/acm/contest/883/F
來源:牛客網
涉及:單調隊列
點擊這裏回到2019牛客暑期多校訓練營解題—目錄貼
題目如下
題目有個細節提示:保證所有情況下的 之和不超過 ,說明我們需要構造一個 的算法。
可以很快想到。用 的複雜度來確定我們考慮的矩形的上下界,即每次找滿足條件的最大矩陣的時候,我們就已經知道這個矩陣的上邊界和下邊界分別位於整個矩陣的第幾行。
即獲得一個當前考慮的區域
for(int i = 1; i <= n; i++){//i爲上邊界
for(int j = i; j <= n; j++){//j爲下邊界
...(求解當前確定邊界的最大矩陣)
}
}
知道上下邊界之後,開始求解當前上下邊界的最大矩陣。
題目所說一個矩陣必須滿足最大值與最小值之差不超過 ,所以在確定上下界的同時,還要動態的獲得當前上下界已確定的目標區域矩陣中的每一列的最大值和最小值
用兩個數組 和 來存當前考慮區域的每一列的最大值和最小值
for(int i = 1; i <= n; i++){//i爲上邊界
memset(ma, 0, sizeof(ma));//初始化ma數組
memset(mi, 0x3f3f3f, sizeof(mi));//初始化mi數組
for(int j = i; j <= n; j++){//j爲下邊界
for(int k = 1; k <= n; k++){//動態求解每一列得到最大值
ma[k] = max(ma[k], a[j][k]);
mi[k] = min(mi[k], a[j][k]);
}
...(求解當前確定邊界的最大矩陣)
}
}
上面操作相當於把多行矩陣中的非最值給忽略,留下來的是單行的最大值矩陣和最小值矩陣
舉個例子:
有了每列最大值矩陣和最小值矩陣,然後就要確定最大矩陣的左右邊界。
可以用單調隊列來實現,於是就變成了單調隊列經典例題
用兩個單調隊列,一個單調遞減隊列維護 數組,一個單調遞增隊列維護 數組。
注意單調隊列維護的是兩個數組得到下標而不是值。每次加入一個值就判斷單調遞減隊列的最大值(隊列首元素)與單調遞增隊列的最小值(隊列首元素)之差是否大於 ,如果大於 就要將隊列首元素出隊列。
最後判斷當前區域的最大矩陣的面積與當前已經求得的最大面積矩陣哪個更大。
int que[3][maxn]; //單調隊列
int l = 1;//最大矩陣左邊界
int l1= 1, r1 = 0;//單調遞減隊列在que數組內的左邊界與右邊界
int l2 = 1, r2 = 0;//單調遞增隊列在que數組內的左邊界與右邊界
for(int r = 1; r <= n; r++){//求當前左邊界能往右延伸多遠
while(l1 <= r1 && ma[r] > ma[que[0][r1]]) r1--;//先將隊列內的非有序元素彈出
que[0][++r1] = r;//放入當前元素
while(l2 <= r2 && mi[r] < mi[que[1][r2]]) r2--;//先將隊列內的非有序元素彈出
que[1][++r2] = r;//放入當前元素
while(l <= r && ma[que[0][l1]]-mi[que[1][l2]] > p){//判斷當前延伸的長度是否合法(之差是否大於M)下面是不合法的處理
l++;//左邊界往右走
while(que[0][l1] < l && l1 <= r1) l1++;//隊列彈出比l左邊界還要靠左的元素(這些元素已經不用再考慮了)
while(que[1][l2] < l && l2 <= r2) l2++;//隊列彈出比l左邊界還要靠左的元素(這些元素已經不用再考慮了)
}
ans = max(ans, (j-i+1)*(r-l+1));//比較答案
}
代碼如下
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 505;
int a[maxn][maxn], t, n, p;//題目所給變量
int ma[maxn], mi[maxn];//上下邊界所確定的區域的每列最大值及最小值
int que[3][maxn]; //兩個單調隊列
int main(){
cin >> t;
while(t--){
int ans = 0;//答案
scanf("%d%d", &n, &p);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
scanf("%d", &a[i][j]);
}
}
for(int i = 1; i <= n; i++){//i爲上邊界
memset(ma, 0, sizeof(ma));//初始化ma數組
memset(mi, 0x3f3f3f, sizeof(mi));//初始化mi數組
for(int j = i; j <= n; j++){//j爲下邊界
for(int k = 1; k <= n; k++){//動態求解每一列得到最大值
ma[k] = max(ma[k], a[j][k]);
mi[k] = min(mi[k], a[j][k]);
}
int l = 1;//最大矩陣左邊界
int l1 = 1, r1 = 0;//單調遞減隊列在que數組內的左邊界與右邊界
int l2 = 1, r2 = 0;//單調遞增隊列在que數組內的左邊界與右邊界
for(int r = 1; r <= n; r++){//求當前左邊界能往右延伸多遠
while(l1 <= r1 && ma[r] > ma[que[0][r1]]) r1--;//先將隊列內的非有序元素彈出
que[0][++r1] = r;//放入當前元素
while(l2 <= r2 && mi[r] < mi[que[1][r2]]) r2--;//先將隊列內的非有序元素彈出
que[1][++r2] = r; //放入當前元素
while(l <= r && ma[que[0][l1]]-mi[que[1][l2]] > p){//判斷當前延伸的長度是否合法(之差是否大於M)下面是不合法的處理
l++;//左邊界往右走
while(que[0][l1] < l && l1 <= r1) l1++;//隊列彈出比l左邊界還要靠左的元素(這些元素已經不用再考慮了)
while(que[1][l2] < l && l2 <= r2) l2++;//隊列彈出比l左邊界還要靠左的元素(這些元素已經不用再考慮了)
}
ans = max(ans, (j-i+1)*(r-l+1));//比較答案
}
}
}
printf("%d\n", ans);
}
return 0;
}