题解:AT_abc311_g [ABC311G] One More Grid Task
ABC311G 题解
题面
题意
给定一个矩阵,求一个子矩阵是的子矩阵中的数字总和与子矩阵中的最小值的乘积最大,输出乘积即可。
前置知识
对于二位前缀和,其实很简单,看个图就懂了。
上图中红色面积=橙色面积+整个的面积-(橙色面积+蓝色面积)-(橙色面积+绿色面积)。
设统计二维前缀和的数组 s[i][j] 为统计 n∑i=1n∑j=1a[i][j]。
则 sum[i][j]=sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1]+a[i][j]。
而一个以 (x,y) 为左上的顶点,(xx,yy) 为右下的顶点的矩形的总和为 sum[x−1][y−1]+sum[x2][y2]−sum[x−1][y2]−sum[x2][y−1]。
思路
首先先考虑一维的,对于一个区间,显然在最小值不变的情况下往左右扩张是最优的,于是就可以用两个单调栈来分别算出左边第一个比 ai 大的数 ali,以及右边第一个比 ai 大的数 ari,于是这时一维的答案为 nmaxi=1ai×ri−1∑j=li+1ai。
于是开始考虑二维,首先能想到的就是把二维压为一维。
发现题目要求的数只有最小值是变化的,子矩阵的和可以根据前置知识里的二位前缀和来求,接下来考虑如何维护最小值。
其实我们可以模拟下二维里左右扩张的过程,假设有一个第 i 列里从第 k 行到第 j 行的一列,其最小值为 mini,则要找左边第一个最小值比 mini 大的第 li 列,和右边第一个最小值比 mini 大的第 ri 列,然后答案即为 mini 乘上子矩阵中的数字和。
那么 min 数组该怎么求呢?如果暴力遍历的话时间复杂度为 O(n4),显然会炸,于是考虑设一个数组 b[i][j][k] 表示第 i 列中第 j 个数到第 k 个数的最小值,而这个数组单调队列预处理一下即可将时间复杂度降为 O(n3),还可以接受。
那么代码应该有能力的人就可以打了。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#define ll long long
using namespace std;
const int MN=305;
ll n,m,a[MN][MN],b[MN][MN][MN],s[MN][MN],ans,l,r,p1[MN],p2[MN];
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
ll gs(ll x, ll y, ll x2, ll y2){return s[x-1][y-1]+s[x2][y2]-s[x-1][y2]-s[x2][y-1];}//求子矩阵的和。
int main(){
n=read();m=read();
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++){
a[i][j]=read();
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//维护二位前缀和。
}
for(int j=1; j<=m; j++){//单调队列预处理 b 数组。
for(int k=1; k<=n; k++){
deque<ll> q[MN];//再循环里定义可以不用清空,下面单调栈同理。
for(int i=1;i<=n;i++){
while(!q[j].empty()&&a[i][j]<=a[q[j].back()][j]) q[j].pop_back();
while(!q[j].empty()&&q[j].front()<=i-k) q[j].pop_front();
q[j].push_back(i);
if(i>=k) b[j][i-k+1][i]=a[q[j].front()][j];
}
}
}
for(int k=1; k<=n; k++){//单调栈左右扩张,统计答案
for(int j=k; j<=n; j++){
stack<ll> s1,s2;
for(int i=m; i>=1; i--){
while(!s1.empty()&&b[s1.top()][k][j]>=b[i][k][j]) s1.pop();
s1.empty()?p1[i]=m+1:p1[i]=s1.top();
s1.push(i);
}
for(int i=1; i<=m; i++){
while(!s2.empty()&&b[s2.top()][k][j]>=b[i][k][j]) s2.pop();
s2.empty()?p2[i]=0:p2[i]=s2.top();
s2.push(i);
}
for(int i=1; i<=m; i++){
r=p1[i]-1;
l=p2[i]+1;
ans=max(ans,gs(k,l,j,r)*b[i][k][j]);
}
}
}
printf("%lld",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现