题解:AT_abc311_g [ABC311G] One More Grid Task

ABC311G 题解

题面

原题传送门

题意

给定一个矩阵,求一个子矩阵是的子矩阵中的数字总和与子矩阵中的最小值的乘积最大,输出乘积即可。

前置知识

  1. 二位前缀和。
  2. 单调栈。
  3. 单调队列。

对于二位前缀和,其实很简单,看个图就懂了。

上图中红色面积=橙色面积+整个的面积-(橙色面积+蓝色面积)-(橙色面积+绿色面积)。

设统计二维前缀和的数组 s[i][j] 为统计 ni=1nj=1a[i][j]

sum[i][j]=sum[i1][j]+sum[i][j1]sum[i1][j1]+a[i][j]

而一个以 (x,y) 为左上的顶点,(xx,yy) 为右下的顶点的矩形的总和为 sum[x1][y1]+sum[x2][y2]sum[x1][y2]sum[x2][y1]

思路

首先先考虑一维的,对于一个区间,显然在最小值不变的情况下往左右扩张是最优的,于是就可以用两个单调栈来分别算出左边第一个比 ai 大的数 ali,以及右边第一个比 ai 大的数 ari,于是这时一维的答案为 nmaxi=1ai×ri1j=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;
}
posted @   naroto2022  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
花开如火,也如寂寞。