首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/883/F
来源:牛客网
涉及:单调队列
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下
题目有个细节提示:保证所有情况下的
N
3
N^3
N3 之和不超过
25
∗
1
0
7
25*10^7
25∗107,说明我们需要构造一个
N
3
N^3
N3 的算法。
可以很快想到。用 N 2 N^2 N2 的复杂度来确定我们考虑的矩形的上下界,即每次找满足条件的最大矩阵的时候,我们就已经知道这个矩阵的上边界和下边界分别位于整个矩阵的第几行。
即获得一个当前考虑的区域
for(int i = 1; i <= n; i++){//i为上边界
for(int j = i; j <= n; j++){//j为下边界
...(求解当前确定边界的最大矩阵)
}
}
知道上下边界之后,开始求解当前上下边界的最大矩阵。
题目所说一个矩阵必须满足最大值与最小值之差不超过 M M M,所以在确定上下界的同时,还要动态的获得当前上下界已确定的目标区域矩阵中的每一列的最大值和最小值
用两个数组 m a [ m a x n ] ma[maxn] ma[maxn] 和 m i [ m a x n ] mi[maxn] mi[maxn] 来存当前考虑区域的每一列的最大值和最小值
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]);
}
...(求解当前确定边界的最大矩阵)
}
}
上面操作相当于把多行矩阵中的非最值给忽略,留下来的是单行的最大值矩阵和最小值矩阵
举个例子:
有了每列最大值矩阵和最小值矩阵,然后就要确定最大矩阵的左右边界。
可以用单调队列来实现,于是就变成了单调队列经典例题
用两个单调队列,一个单调递减队列维护 m a ma ma 数组,一个单调递增队列维护 m i mi mi 数组。
注意单调队列维护的是两个数组得到下标而不是值。每次加入一个值就判断单调递减队列的最大值(队列首元素)与单调递增队列的最小值(队列首元素)之差是否大于 M M M,如果大于 M M M 就要将队列首元素出队列。
最后判断当前区域的最大矩阵的面积与当前已经求得的最大面积矩阵哪个更大。
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;
}