P2219 [HAOI2007]修筑绿化带
首先显然我们可以维护一个二维前缀和 $O(1)$ 求出任意一个矩形的值
考虑枚举大矩形的左上角,并维护当前矩形中,小矩形的最小值
放一个图:
维护小矩形最小值先考虑暴力怎么搞
同样考虑枚举左上角,那么大概枚举过程可以长成这个样子:
发现可以先预处理出同一排竖下来的小矩形最小值,显然这个可以直接单调队列维护
然后对于同一横排的大矩形的小矩形最小值就可以用预处理出的竖的一段小矩形最小值搞单调队列同样维护
然后是一些边界的细节问题了,注意大矩形必须完全包含小矩形,边界不能重叠!
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> using namespace std; typedef long long ll; inline int read() { int 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^48); ch=getchar(); } return x*f; } const int N=2e3+7; int n,m,A,B,C,D,ans; int sum[N][N],E[N][N],F[N][N]; int G[N][N]; int Q[N]; inline int SUM(int xa,int ya,int xb,int yb) { return sum[xb][yb]-sum[xa-1][yb]-sum[xb][ya-1]+sum[xa-1][ya-1]; } //求以(xa,ya)为左上角,(xb,yb)为右下角的矩形的值 int main() { n=read(),m=read(),A=read(),B=read(),C=read(),D=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+read(); for(int i=1;i+A-1<=n;i++) for(int j=1;j+B-1<=m;j++) E[i][j]=SUM(i,j,i+A-1,j+B-1);//求大矩形的值 for(int i=1;i+C-1<=n;i++) for(int j=1;j+D-1<=m;j++) F[i][j]=SUM(i,j,i+C-1,j+D-1);//求小矩形的值 int l,r,p; for(int j=2;j+D-1<m;j++)//求竖的一段小矩形的最小值 { l=1,r=0,p=2; for(int i=1;i+A-1<=n;i++) { while(Q[l]<=i&&l<=r) l++; while(p<i+A-C) { while(F[Q[r]][j]>=F[p][j]&&l<=r) r--; Q[++r]=p; p++; } G[i][j]=F[Q[l]][j]; } } for(int i=1;i+A-1<=n;i++) { l=1,r=0,p=2; for(int j=1;j+B-1<=m;j++) { while(Q[l]<=j&&l<=r) l++; while(p<j+B-D) { while(G[i][Q[r]]>=G[i][p]&&l<=r) r--; Q[++r]=p; p++; } ans=max(ans,E[i][j]-G[i][Q[l]]); } } printf("%d",ans); return 0; }