题解:AT_abc311_g [ABC311G] One More Grid Task

ABC311G 题解

题面

原题传送门

题意

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

前置知识

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

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

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

设统计二维前缀和的数组 \(s[i][j]\) 为统计 \(\begin{aligned} \sum_{i=1}^n\sum_{j=1}^n a[i][j] \end{aligned}\)

\(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]\)

思路

首先先考虑一维的,对于一个区间,显然在最小值不变的情况下往左右扩张是最优的,于是就可以用两个单调栈来分别算出左边第一个比 \(a_i\) 大的数 \(a_{l_i}\),以及右边第一个比 \(a_i\) 大的数 \(a_{r_i}\),于是这时一维的答案为 \(\begin{aligned}\max_{i=1}^n a_i\times\sum_{j=l_i+1}^{r_i-1} a_i\end{aligned}\)

于是开始考虑二维,首先能想到的就是把二维压为一维。

发现题目要求的数只有最小值是变化的,子矩阵的和可以根据前置知识里的二位前缀和来求,接下来考虑如何维护最小值。

其实我们可以模拟下二维里左右扩张的过程,假设有一个第 \(i\) 列里从第 \(k\) 行到第 \(j\) 行的一列,其最小值为 \(min_i\),则要找左边第一个最小值比 \(min_i\) 大的第 \(l_i\) 列,和右边第一个最小值比 \(min_i\) 大的第 \(r_i\) 列,然后答案即为 \(min_i\) 乘上子矩阵中的数字和。

那么 \(min\) 数组该怎么求呢?如果暴力遍历的话时间复杂度为 \(O(n^4)\),显然会炸,于是考虑设一个数组 \(b[i][j][k]\) 表示第 \(i\) 列中第 \(j\) 个数到第 \(k\) 个数的最小值,而这个数组单调队列预处理一下即可将时间复杂度降为 \(O(n^3)\),还可以接受。

那么代码应该有能力的人就可以打了。

代码

#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 @ 2025-01-29 15:51  naroto2022  阅读(21)  评论(0)    收藏  举报