//本题考查单调队列的应用,核心是维护每一行/列的个滑动窗口的最值
//对于每一行(列),使用单调队列预处理出每个长为b(a)的滑动窗口的最值
//再枚举原矩阵每一列,处理出每个子矩阵的最值即可
//注:单调队列是保证队内元素递增/减有序的队列,在入队时弹出破坏顺序的元素
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
const int mod=998244353;
deque<int>dq;//双端队列实现单调队列
int matrix[N][N];//存储原始矩阵
int n,m,a,b;
int MAX[N][N],MIN[N][N];//MAX[i][j]存储第i行在[j-(b-1),j]范围内的最大值,MIN同理
void get_max(int A[],int B[],int len,int k)
//对于长度为len的数组A,求长度为k的各滑动窗口的最大值,结果存入数组B
{
dq.clear();//清空双端队列
for(int i=1;i<=len;i++)//遍历全部元素
{
while(!dq.empty()&&A[i]>=A[dq.back()])//队列非空,当前元素大于队尾
{
dq.pop_back();//队尾元素不断出队直至当前队尾更大
}
dq.push_back(i);//当前元素下标入队
if(dq.front()<i-k+1)dq.pop_front();//队首存储的下标移出滑动窗口左端,调整队首
B[i]=A[dq.front()];//保存当前滑动窗口的最大值
}
//例如对于A[10] = 4 1 9 8 2 7 6 10 5 3,设k=5, 则B[10] = 4 4 9 9 9 9 9 10 10 10
//从B[5]开始每个B[i]都表示区间[i-(k-1),i]内的最大值
}
void get_min(int A[],int B[],int len,int k)//同上,略
{
dq.clear();
for(int i=1;i<=len;i++)
{
while(!dq.empty()&&A[i]<=A[dq.back()])dq.pop_back();
dq.push_back(i);
if(dq.front()<i-k+1)dq.pop_front();
B[i]=A[dq.front()];
}
}
int main()
{
ll sum=0;
scanf("%d%d%d%d",&n,&m,&a,&b);
for(int i=1;i<=n;i++)//输入原始矩阵
{
for(int j=1;j<=m;j++)scanf("%d",&matrix[i][j]);
}
//对原矩阵的每一行,长为m,滑动窗口长为b,求各滑动窗口的最大/小值,存入MAX/MIN矩阵的第i行
for(int i=1;i<=n;i++)get_max(matrix[i],MAX[i],m,b);
for(int i=1;i<=n;i++)get_min(matrix[i],MIN[i],m,b);
int temp[N],matrix_max[N],matrix_min[N];
for(int i=b;i<=m;i++)//从第b列开始枚举每一列i
{
//对于原矩阵的每一行j,取其区间[i-(b-1),i]内的最大值,存入temp
for(int j=1;j<=n;j++)temp[j]=MAX[j][i];
//对这n个最大值,滑动窗口长为a,求各滑动窗口的最大值,即各子矩阵的最大值
get_max(temp,matrix_max,n,a);
for(int j=1;j<=n;j++)temp[j]=MIN[j][i];//同上,略
get_min(temp,matrix_min,n,a);
//经过以上步骤,在j>=a时,matrix_max[j]存储[j-(a-1),j]区间内最大值,matrix_min同理
for(int j=a;j<=n;j++)//累加即可
{
sum=(sum+(ll)(matrix_max[j]*matrix_min[j]%mod))%mod;
}
}
printf("%lld\n",sum);
return 0;
}
子矩阵 蓝桥杯 单调队列 二维滑动窗口
于 2024-04-10 10:56:39 首次发布