[ABC311G] One More Grid Task

[ABC311G] One More Grid Task

题目信息

题面翻译

给你一个 n×m 的矩阵 a,求:

max1l1r1n,1l2r2m(l1ir1,l2jr2ai,j×minl1ir1,l2jr2ai,j)

题目描述

N × M のグリッドがあり、上から i 行目、左から j 列目のマス (i,j) には非負整数 Ai,j が書かれています。
このグリッドのうち長方領域をひとつ選び、それを R とします。
厳密には、長方領域は以下の手順で選ばれます。

  • 1  lx  rx  N, 1  ly  ry  M なる整数 lx, rx, ly, ry を選ぶ。
  • このとき、整数 i,jlx  i  rx かつ ly  j  ry を満たす、またその時に限って、マス (i,j)R に含まれる。

適切に R を選ぶことによって、 f(R) = ( R 内のマスに書かれた整数の総和 ) × ( R 内のマスに書かれた整数の最小値 ) として達成可能な最大値を求めてください。

输入格式

入力は以下の形式で標準入力から与えられる。

N M A1,1 A1,2 A1,M A2,1 A2,2 A2,M AN,1 AN,2 AN,M

输出格式

答えを整数として出力せよ。

样例 #1

样例输入 #1

3 3
5 4 3
4 3 2
3 2 1

样例输出 #1

48

样例 #2

样例输入 #2

4 5
3 1 4 1 5
9 2 6 5 3
5 8 9 7 9
3 2 3 8 4

样例输出 #2

231

样例 #3

样例输入 #3

6 6
1 300 300 300 300 300
300 1 300 300 300 300
300 300 1 300 300 300
300 300 300 1 300 300
300 300 300 300 1 300
300 300 300 300 300 1

样例输出 #3

810000

提示

制約

  • 入力は全て整数
  • 1  N,M  300
  • 1  Ai,j  300

Sample Explanation 1

左上がマス (1,1) 、右下がマス (2,2) の長方領域を選ぶことで、 f(R) = (5+4+4+3) × min(5,4,4,3) = 48 が達成でき、これが達成可能な最大値です。

题目思路

算法一

我们可以考虑直接枚举左上角,枚举右下角,暴力求和以及暴力求最小值。

时间复杂度:O(n3m3).

算法二

算法一显然不行。我们可以提前预处理,用上数据结构(比如说树状数组、线段树树),直接求最小值和和。

时间复杂度:O(n2m2lognlogm)

算法三

算法二显然也不行。考虑优化。

我们知道:任何矩阵的问题都可以压缩成一维问题。即枚举上界、枚举下界,求方案数。

预处理后,我们可以记录 leftirighti 分别代表能延伸的最左边,能延伸的最右边。

直接使用单调栈维护即可。

时间复杂度:O(n2mlogn)

代码

// LUOGU_RID: 161862093
#include<bits/stdc++.h>
#define int long long
#define left _zqh_
#define right flying_hq
using namespace std;
const int MAXN = 320;
const int LOGN = log2(MAXN)+5;
int n,m,matrix[MAXN][MAXN],ans;
void read(int &x){
x = 0;int p = 1;char ch;
do{
ch = getchar();
if(ch=='-') p = -1;
}while(!isdigit(ch));
while(isdigit(ch)){
x*=10;
x+=ch-'0';
ch = getchar();
}
x*=p;
}
int sum[MAXN][MAXN],now[MAXN],left[MAXN],right[MAXN],q[MAXN],minn[MAXN];
class Segmenttree{
private:
struct segment{
int l,r,min=0X3F3F3F3F;
}tree[MAXN<<3];
void pushup(int id){
tree[id].min = min(tree[id*2].min,tree[id*2+1].min);
}
public:
void build(int k,int l,int r){
tree[k].l = l;
tree[k].r = r;
if(l==r) return;
int mid = (l+r)>>1;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
}
void change(int k,int p,int q){
if(tree[k].l>p||tree[k].r<p) return;
if(tree[k].l>=p&&tree[k].r<=p){
tree[k].min = q;
return;
}
change(k*2,p,q);
change(k*2+1,p,q);
pushup(k);
}
int getmin(int k,int l,int r){
if(tree[k].l>r||tree[k].r<l) return 0x3f3f3f3f;
if(tree[k].l>=l&&tree[k].r<=r){
return tree[k].min;
}
return min(getmin(k*2,l,r),getmin(k*2+1,l,r));
}
}tree[MAXN];
signed main(){
read(n);read(m);
for(int i = 1;i<=m;i++) tree[i].build(1,1,n);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
read(matrix[i][j]);
sum[i][j] = sum[i-1][j]+matrix[i][j];
tree[j].change(1,i,matrix[i][j]);
}
}
for(int i = 1;i<=n;i++){
for(int j = i;j<=n;j++){
if(i == 1&&j== 2){
int k = 0;
}
for(int k = 1;k<=m;k++){
now[k] = sum[j][k]-sum[i-1][k];
q[k] = q[k-1]+now[k];
minn[k] = tree[k].getmin(1,i,j);
}
stack<pair<int,int>> st;
for(int k = 1;k<=m;k++){
while(!st.empty()&&st.top().second>=minn[k]) st.pop();
if(st.empty()){
left[k] = 1;
}else{
left[k] = st.top().first+1;
}
st.push({k,minn[k]});
}
while(!st.empty()){
st.pop();
}
for(int k = m;k>=1;k--){
while(!st.empty()&&st.top().second>=minn[k]) st.pop();
if(st.empty()){
right[k] = m;
}else{
right[k] = st.top().first-1;
}
st.push({k,minn[k]});
}
for(int k = 1;k<=m;k++){
ans = max(ans,(q[right[k]]-q[left[k]-1])*minn[k]);
}
}
}
printf("%lld",ans);
return 0;
}

tag

Atcoder题解
单调栈线段树树状数组数据结构

posted @   辜铜星  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示